{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "350fb575",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'> Modificado en 2024-1 al 2026-2 por Equipo Docente IIC2233. </font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f1aa21e",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [*Server-side App*](#Server-side-App)\n",
    "    1. [Flask](#Flask)\n",
    "        1. [Ejemplo básico](#Ejemplo-básico)\n",
    "        2. [Ejemplo Avanzado](#Ejemplo-Avanzado)\n",
    "            1. [Caso 1: _endpoint_ dinámico](#Caso-1:-endpoint-dinámico)\n",
    "            1. [Caso 2: argumentos en la URL](#Caso-2:-argumentos-en-la-URL)\n",
    "            1. [Caso 3: datos en el _body_](#Caso-3:-datos-en-el-body)\n",
    "            1. [Caso 4: manejo de errores y respuestas personalizadas](#Caso-4:-manejo-de-errores-y-respuestas-personalizadas)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54daefb3",
   "metadata": {},
   "source": [
    "## *Server-side App*\n",
    "\n",
    "La misión principal del servidor es disponer el contenido para que pueda ser consultado mediante un *web service*. La aplicación que corre en el servidor es la encargada de la lógica e interacción entre cliente-servidor. La información que viaja entre un cliente y un servidor permite generar comunicación entre aplicaciones.\n",
    "\n",
    "Una aplicación puede estar desarrollada en cualquier lenguaje de programación que permita exponer una API para ser consumida por otras aplicaciones a través de la web. Por ejemplo, podemos tener una aplicación corriendo en Java, y desde nuestro código en Python acceder a esa API.\n",
    "\n",
    "En Python existe [WSGI](https://docs.python.org/es/3/library/wsgiref.html) para exponer APIs. Tambien existen varios *frameworks* de programación que facilitan esta misma tarea, como **Flask** y **Django**. Además, puedes montar tus aplicaciones en servicios o servidores ya disponibles en la web, provistos como Platform-as-a-Service (PaaS) o Infrastructure-as-a-Service (IaaS). Por ejemplo, puedes usar **Heroku** (PaaS), **Digital Ocean** (IaaS), o **Microsoft Azure** (PaaS) para disponer tus APIs en una red pública con alta disponibilidad.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93fb1318",
   "metadata": {},
   "source": [
    "A modo de ejemplo, tenemos una pequeña API utilizando WSGI para levantar una aplicación que responde algunos mensajes. Este código **NO utiliza frameworks como Flask o Django** para implementar los distintos endpoints, el manejo de consultas y la generación de respuestas, por lo que es distinto a lo que se verá en el resto del _notebook_.  \n",
    "\n",
    "Antes de empezar, debes ejecutar el archivo `servidor-API.py` presente en la carpeta `codigo/ejemplo_sin_flask` en la terminal. Luego, puedes ejecutar las siguientes celdas donde utilizaremos `requests` para realizar solicitudes a esta API."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "46ff88a5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "200"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import requests\n",
    "\n",
    "# La URL base de la API creada\n",
    "BASE_URL = \"http://localhost:4444/\"\n",
    "\n",
    "# Podemos consultar a esta ruta\n",
    "respuesta = requests.get(BASE_URL)\n",
    "respuesta.status_code"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "de319089",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'mensaje': 'Hello World'}"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8a41a148",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "200 {'mensaje': 'Que la fuerza esté contigo'}\n"
     ]
    }
   ],
   "source": [
    "# ¡Vamos a despedirnos haciendo una consulta a otro endpoint de nuestra API!\n",
    "respuesta = requests.get(BASE_URL + \"goodbye/\")\n",
    "print(respuesta.status_code, respuesta.json())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec21d858",
   "metadata": {},
   "source": [
    "## Flask"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05bd0adf-1f4d-4e8e-a668-f0c3fad262eb",
   "metadata": {},
   "source": [
    "En esta sección veremos, desde el punto de vista del servidor, cómo crear una API que podrá ser consumida mendiante `requests`. En Python, la librería `flask` nos permite levantar una API y asociar diferentes funciones a los métodos (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) y los _endpoints_ deseados.\n",
    "\n",
    "Para instalar la librería `flask`, en cualquier terminal debes correr el comando `python3 -m pip install flask`. Otra opción, es ejecutar la siguiente celda, que instalará la librería en el mismo entorno en que estés corriendo este jupyter (de todas maneras recomendamos instalarlo desde la consola).\n",
    "\n",
    "Recuerda que si `python3` no funciona, probar con `python`, `py` o `py3`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "224586ac-21c8-4420-8274-a9983a575ae2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: flask in /home/jimmy/Documents/Avanzada/serializacion y networking/.venv/lib/python3.12/site-packages (3.1.3)\n",
      "Requirement already satisfied: blinker>=1.9.0 in /home/jimmy/Documents/Avanzada/serializacion y networking/.venv/lib/python3.12/site-packages (from flask) (1.9.0)\n",
      "Requirement already satisfied: click>=8.1.3 in /home/jimmy/Documents/Avanzada/serializacion y networking/.venv/lib/python3.12/site-packages (from flask) (8.3.3)\n",
      "Requirement already satisfied: itsdangerous>=2.2.0 in /home/jimmy/Documents/Avanzada/serializacion y networking/.venv/lib/python3.12/site-packages (from flask) (2.2.0)\n",
      "Requirement already satisfied: jinja2>=3.1.2 in /home/jimmy/Documents/Avanzada/serializacion y networking/.venv/lib/python3.12/site-packages (from flask) (3.1.6)\n",
      "Requirement already satisfied: markupsafe>=2.1.1 in /home/jimmy/Documents/Avanzada/serializacion y networking/.venv/lib/python3.12/site-packages (from flask) (3.0.3)\n",
      "Requirement already satisfied: werkzeug>=3.1.0 in /home/jimmy/Documents/Avanzada/serializacion y networking/.venv/lib/python3.12/site-packages (from flask) (3.1.8)\n"
     ]
    }
   ],
   "source": [
    "!python3 -m pip install flask"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a71eb13-6908-4e70-bd1f-d7bff05b7042",
   "metadata": {},
   "source": [
    "### Ejemplo básico"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e501119a-1f81-48ed-908f-4432dd22662e",
   "metadata": {},
   "source": [
    "La sintaxis básica para levantar una aplicación con flask es:\n",
    "\n",
    "```python\n",
    "from flask import Flask, request\n",
    "import random\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "@app.route(\"/\", methods=[\"GET\"])\n",
    "def hello_world():\n",
    "    return {\"texto\": \"Hello, World!\"}\n",
    "\n",
    "\n",
    "@app.route(\"/numero_aleatorio\", methods=[\"GET\", \"POST\"])\n",
    "def numero_aleatorio():\n",
    "    if request.method == \"POST\":\n",
    "        numero = random.randint(0, 6)\n",
    "        return {\"texto\": f\"Tu número es: {numero}\", \"método\": \"POST\"}\n",
    "\n",
    "    numero = random.randint(-4444, -11)\n",
    "    return {\"texto\": f\"Tu número es: {numero}\", \"método\": \"GET\"}\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    app.run(host=\"localhost\", port=4444)\n",
    "```\n",
    "\n",
    "\n",
    "1. Primero hacemos `import` de la librería.\n",
    "2. Creamos una instancia de la aplicación mediante `Flask(__name__)`. El uso de `__name__` es para que la aplicación tenga el nombre del archivo que contiene la app.\n",
    "3. Para definir los distintos _endpoints_ de la API:\n",
    "    - Usamos `@app.route` para definir el _endpoint_ y el o los métodos que permiten acceder a la función.\n",
    "    - Luego, definimos la función que se va a ejecutar y retornaremos la respuesta a la consulta.\n",
    "    - Adentro de la función, podemos usar `request.method` para identificar el método utilizado para llamar a dicha función.\n",
    "4. Dentro del `__name__ == \"__main__\"` hacemos `app.run` para ejecutar el _script_.\n",
    "\n",
    "Finalmente, en este curso disponemos de 2 opciones para ejecutar una aplicación con `Flask`:\n",
    "1. Usar el comando `python` para ejecutar el archivo, por ejemplo, `python3 servidor_1.py`\n",
    "2. Usar el comando `flask` y `run` para levantar la API, por ejemplo, `flask --app servidor_1 run --port 4444`. Si no se indica el puerto, en esta opción se utilizará el puerto por defecto de flask, el 5000.\n",
    "\n",
    "En ambos casos, aparecerá el siguiente mensaje:\n",
    "> WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.\n",
    "\n",
    "¡No te preocupes! Esto es porque cuando uno quiere levantar una API para el público, se utilizan WSGI para ejecutar y exponer una API creada por `Flask`.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17c4fb03-4eb9-4072-815c-acc857eb5089",
   "metadata": {},
   "source": [
    "**Probando la API**\n",
    "\n",
    "La siguiente celda inicia el servidor directamente desde el notebook (sin necesidad de abrir una terminal). Una vez iniciado, dispondremos los siguientes _endpoints_:\n",
    "- (`GET`) `http://localhost:4444/`:  ejecutará la función `hello_world`.\n",
    "- (`GET`) `http://localhost:4444/numero_aleatorio`:  retornará un número aleatorio entre **-4444 y -11**.\n",
    "- (`POST`) `http://localhost:4444/numero_aleatorio`:  retornará un número aleatorio entre **0 y 6**.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "be7475b1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Servidor 1 iniciado en http://localhost:4444\n"
     ]
    }
   ],
   "source": [
    "import threading\n",
    "import random\n",
    "import logging\n",
    "from werkzeug.serving import make_server\n",
    "from flask import Flask\n",
    "# Nota: 'request' es el objeto de Flask (singular),\n",
    "# distinto de la librería de HTTP 'requests' (plural)\n",
    "from flask import request as flask_req\n",
    "\n",
    "logging.getLogger(\"werkzeug\").setLevel(logging.ERROR)  # suprimir logs de werkzeug\n",
    "\n",
    "app_1 = Flask(\"servidor_1\")\n",
    "\n",
    "@app_1.route(\"/\", methods=[\"GET\"])\n",
    "def hello_world():\n",
    "    return {\"texto\": \"Hello, World!\"}\n",
    "\n",
    "@app_1.route(\"/numero_aleatorio\", methods=[\"GET\", \"POST\"])\n",
    "def numero_aleatorio():\n",
    "    if flask_req.method == \"POST\":\n",
    "        numero = random.randint(0, 6)\n",
    "        return {\"texto\": f\"Tu número es: {numero}\", \"método\": \"POST\"}\n",
    "    numero = random.randint(-4444, -11)\n",
    "    return {\"texto\": f\"Tu número es: {numero}\", \"método\": \"GET\"}\n",
    "\n",
    "servidor_1 = make_server(\"localhost\", 4444, app_1)\n",
    "threading.Thread(target=servidor_1.serve_forever, daemon=True).start()\n",
    "print(\"Servidor 1 iniciado en http://localhost:4444\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "e7ee7e08-0e8f-467e-b48c-c7468e782bf1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'texto': 'Hello, World!'}"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import requests\n",
    "\n",
    "respuesta = requests.get(\"http://localhost:4444/\")\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "8b76e20f-5dcf-407b-b28b-73c16c8d4415",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'método': 'GET', 'texto': 'Tu número es: -1409'}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta = requests.get(\"http://localhost:4444/numero_aleatorio\")\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "c4402474-818e-462f-bc87-9df922b5bc24",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'método': 'POST', 'texto': 'Tu número es: 6'}"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta = requests.post(\"http://localhost:4444/numero_aleatorio\")\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "d2eac5c9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Servidor 1 detenido\n"
     ]
    }
   ],
   "source": [
    "servidor_1.shutdown()\n",
    "print(\"Servidor 1 detenido\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f9330101-ff96-42ea-8d27-80b5839bc665",
   "metadata": {},
   "source": [
    "### Ejemplo Avanzado"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "922f108e-4960-4e78-934f-8a99019e1fd6",
   "metadata": {},
   "source": [
    "Cuando consumimos una API, a veces necesitamos o queremos:\n",
    "* Que algún _endpoint_ cambie dinámicamente. \n",
    "* Obtener los argumentos entregados en la consulta. \n",
    "* Enviar datos como argumentos y/o dentro del _body_.\n",
    "* Manejar errores y/o tener un mayor control de las respuestas que se mandan (códigos de respuesta, tipo de contenido).\n",
    "\n",
    "Vamos a ver un ejemplo y entender cada función:\n",
    "\n",
    "```python\n",
    "import json\n",
    "from flask import Flask, Response, request, abort\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "# Caso 1: endpoint dinámico\n",
    "@app.route(\"/hi/<string:username>\", methods=[\"GET\"])\n",
    "def saludar(username):\n",
    "    return {\"texto\": f\"Hola {username}!\"}\n",
    "\n",
    "\n",
    "# Caso 2: argumentos en la URL\n",
    "@app.route(\"/argumentos\", methods=[\"GET\"])\n",
    "def argumentos():\n",
    "    nombre = request.args.get(\"nombre\", default=\"Guillermo\", type=str)\n",
    "    especie = request.args.get(\"especie\", default=\"Gato\", type=str)\n",
    "    edad = request.args.get(\"edad\", default=0, type=int)\n",
    "    return {\"texto\": f\"Hola {especie} {nombre} de {edad} años!\"}\n",
    "\n",
    "\n",
    "# Caso 3: datos en el body\n",
    "@app.route(\"/body\", methods=[\"POST\"])\n",
    "def datos_en_body():\n",
    "    body_data = request.get_json(force=True)\n",
    "    numero_1 = body_data[\"var_1\"]\n",
    "    numero_2 = body_data[\"var_2\"]\n",
    "    resultado = numero_1 + numero_2\n",
    "    return {\"var_1\": numero_1, \"var_2\": numero_2, \"result\": resultado}\n",
    "\n",
    "\n",
    "# Caso 4: manejar errores y/o tener más control de las respuestas que se entregan\n",
    "@app.route(\"/error/<int:instruccion>\", methods=[\"DELETE\"])\n",
    "def error(instruccion):\n",
    "    if instruccion == 0:\n",
    "        abort(400)\n",
    "    if instruccion == 1:\n",
    "        return Response(status=418)\n",
    "    if instruccion == 2:\n",
    "        return Response(status=200, response='Podemos mandar textos', content_type='text/plain')\n",
    "    if instruccion == 3:\n",
    "        respuesta = json.dumps({'mensaje': 'También JSONs'})\n",
    "        return Response(status=202, response=respuesta, content_type='application/json')\n",
    "\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    app.run(host=\"localhost\", port=4444)\n",
    "```\n",
    "\n",
    "\n",
    "La siguiente celda inicia este servidor directamente desde el notebook.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "0b6968ea",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Servidor 2 iniciado en http://localhost:4444\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "import threading\n",
    "import logging\n",
    "from werkzeug.serving import make_server\n",
    "from flask import Flask, Response, abort\n",
    "from flask import request as flask_req\n",
    "\n",
    "logging.getLogger(\"werkzeug\").setLevel(logging.ERROR)\n",
    "\n",
    "app_2 = Flask(\"servidor_2\")\n",
    "\n",
    "@app_2.route(\"/hi/<string:username>\", methods=[\"GET\"])\n",
    "def saludar(username):\n",
    "    return {\"texto\": f\"Hola {username}!\"}\n",
    "\n",
    "@app_2.route(\"/argumentos\", methods=[\"GET\"])\n",
    "def argumentos():\n",
    "    nombre = flask_req.args.get(\"nombre\", default=\"Guillermo\", type=str)\n",
    "    especie = flask_req.args.get(\"especie\", default=\"Gato\", type=str)\n",
    "    edad   = flask_req.args.get(\"edad\",   default=0,           type=int)\n",
    "    return {\"texto\": f\"Hola {especie} {nombre} de {edad} años!\"}\n",
    "\n",
    "@app_2.route(\"/body\", methods=[\"POST\"])\n",
    "def datos_en_body():\n",
    "    body_data = flask_req.get_json(force=True)\n",
    "    numero_1 = body_data[\"var_1\"]\n",
    "    numero_2 = body_data[\"var_2\"]\n",
    "    return {\"var_1\": numero_1, \"var_2\": numero_2, \"result\": numero_1 + numero_2}\n",
    "\n",
    "@app_2.route(\"/error/<int:instruccion>\", methods=[\"DELETE\"])\n",
    "def error(instruccion):\n",
    "    if instruccion == 0:\n",
    "        abort(400)\n",
    "    if instruccion == 1:\n",
    "        return Response(status=418)\n",
    "    if instruccion == 2:\n",
    "        return Response(status=200, response=\"Podemos mandar textos\", content_type=\"text/plain\")\n",
    "    if instruccion == 3:\n",
    "        resp = json.dumps({\"mensaje\": \"También JSONs\"})\n",
    "        return Response(status=202, response=resp, content_type=\"application/json\")\n",
    "\n",
    "servidor_2 = make_server(\"localhost\", 4444, app_2)\n",
    "threading.Thread(target=servidor_2.serve_forever, daemon=True).start()\n",
    "print(\"Servidor 2 iniciado en http://localhost:4444\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83d9319d-fc80-4968-9a63-78a53d1e2c15",
   "metadata": {},
   "source": [
    "#### Caso 1: _endpoint_ dinámico\n",
    "\n",
    "```python\n",
    "@app.route(\"/hi/<string:username>\", methods=[\"GET\"])\n",
    "def saludar(username):\n",
    "    return {\"texto\": f\"Hola {username}!\"}\n",
    "```\n",
    "\n",
    "En este caso, cuando se hace `\"<string:username>\"` dentro del _endpoint_, se interpreta que dicha sección de la URL puede ir mutando. Por ejemplo, hacer `\"/hi/Luna\"`, `\"/hi/Cachirulo\"`, `\"/hi/Guillermo\"` harán _match_ con este _endpoint_ y ejecutarán la función `saludar`.\n",
    "\n",
    "A continuación vamos a probar este _endpoint_ con diferentes nombres:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "bccf9b2a-536a-4f3c-89c1-1d7d4c7c6a1c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'texto': 'Hola Luna!'}"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import requests\n",
    "\n",
    "respuesta = requests.get(\"http://localhost:4444/hi/Luna\")\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "b4d88647-cc0a-4165-92f7-c28be90607a0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'texto': 'Hola Cachirulo!'}"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta = requests.get(\"http://localhost:4444/hi/Cachirulo\")\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "e489433d-55a8-4bd6-a6e4-86b722171197",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'texto': 'Hola Guillermo!'}"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta = requests.get(\"http://localhost:4444/hi/Guillermo\")\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4915adf9-729d-451c-a394-41195ce626dd",
   "metadata": {},
   "source": [
    "#### Caso 2: argumentos en la URL\n",
    "\n",
    "```python\n",
    "@app.route(\"/argumentos\", methods=[\"GET\"])\n",
    "def argumentos():\n",
    "    nombre = request.args.get(\"nombre\", default=\"Guillermo\", type=str)\n",
    "    especie = request.args.get(\"especie\", default=\"Gato\", type=str)\n",
    "    edad = request.args.get(\"edad\", default=0, type=int)\n",
    "    return {\"texto\": f\"Hola {especie} {nombre} de {edad} años!\"}\n",
    "```\n",
    "\n",
    "En este caso, cuando agregamos argumentos en la URL (por ejemplo `\"/args?name=Lucky\"`) se debe utilizar `request.args` para acceder a dicha información. En particular, como `request.args` es un diccionario, usamos el método `.get(key, valor_por_defecto, tipo_de_dato)` para obtener el argumento cuya llave sea `key`, transformarlo al `tipo_de_dato` entregado, y -en caso de no existir dicho argumento- usamos el valor por defecto.\n",
    "\n",
    "A continuación vamos a probar este _endpoint_ con diferentes argumentos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "30d26bc3-f4ee-45f5-b8b0-2f3799af74eb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'texto': 'Hola Tortuga Pepa de 70 años!'}"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta = requests.get(\"http://localhost:4444/argumentos?nombre=Pepa&especie=Tortuga&edad=70\")\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "73f65efa-cd65-4b51-8107-db44abc8d990",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'texto': 'Hola Gato Cachirulo de 5 años!'}"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta = requests.get(\"http://localhost:4444/argumentos?nombre=Cachirulo&edad=5\")\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "d0b5267c-5b12-4457-b90d-e0f8a8ca3d4e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'texto': 'Hola Gato Guillermo de 0 años!'}"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta = requests.get(\"http://localhost:4444/argumentos\")\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "1572538e-5864-47c5-a536-b818822763c7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'texto': 'Hola Gato Luna de 8 años!'}"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "parametros = {\"nombre\": \"Luna\", \"edad\": 8, \"informacion_extra\": \"Le gusta el churu\"}\n",
    "\n",
    "respuesta = requests.get(\"http://localhost:4444/argumentos\", params=parametros)\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3afa19b-7038-4483-8067-206bbcb23237",
   "metadata": {},
   "source": [
    "#### Caso 3: datos en el _body_\n",
    "\n",
    "```python\n",
    "@app.route(\"/body\", methods=[\"POST\"])\n",
    "def datos_en_body():\n",
    "    body_data = request.get_json(force=True)\n",
    "    numero_1 = body_data[\"var_1\"]\n",
    "    numero_2 = body_data[\"var_2\"]\n",
    "    resultado = numero_1 + numero_2\n",
    "    return {\"var_1\": numero_1, \"var_2\": numero_2, \"result\": resultado}\n",
    "```\n",
    "\n",
    "En este caso, cuando agregamos datos en el _body_, existen varias formas de tratar con esta información, pero una de estas es usar `request.get_json(force=True)` para acceder a dicha información. En particular, usamos `force=True` para obligar que cualquier información enviada en `data` se transforme en un JSON.\n",
    "\n",
    "A continuación vamos a probar este _endpoint_ con diferentes argumentos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "7853fb18-099a-4eb5-9e8d-2e9d98ec7e21",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'result': 24, 'var_1': 11, 'var_2': 13}"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import json\n",
    "\n",
    "body = {\n",
    "    'var_1': 11,\n",
    "    'var_2': 13\n",
    "}\n",
    "\n",
    "respuesta = requests.post(\"http://localhost:4444/body\", data=json.dumps(body))\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "db91f896-7836-4754-b06a-a48be51bd1ef",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'result': 4444, 'var_1': 4444, 'var_2': 0}"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "body = {\n",
    "    'var_1': 4444,\n",
    "    'var_2': 0\n",
    "}\n",
    "\n",
    "respuesta = requests.post(\"http://localhost:4444/body\", data=json.dumps(body))\n",
    "respuesta.json()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66e375e6",
   "metadata": {},
   "source": [
    "#### Caso 4: manejo de errores y respuestas personalizadas\n",
    "\n",
    "\n",
    "```python\n",
    "@app.route(\"/error/<int:instruccion>\", methods=[\"DELETE\"])\n",
    "def error(instruccion):\n",
    "    if instruccion == 0:\n",
    "        abort(400)\n",
    "    if instruccion == 1:\n",
    "        return Response(status=418)\n",
    "    if instruccion == 2:\n",
    "        return Response(status=200, response='Podemos mandar textos', content_type='text/plain')\n",
    "    if instruccion == 3:\n",
    "        respuesta = json.dumps({'mensaje': 'También JSONs'})\n",
    "        return Response(status=202, response=respuesta, content_type='application/json')\n",
    "```\n",
    "\n",
    "En este caso vemos distintas forma de generar respuestas a nuestras consulta:\n",
    "* `abort(codigo)` recibe un código de estado y -al igual que un `raise`- levanta una excepción (es específico, `HTTPException`) que termina la ejecución de la función y envía una respuesta con el código recibido.\n",
    "* `Response()` corresponde al objeto que es enviado como respuesta a una consulta. Por lo general, cuando el `return` de una función recibe algo distinto a una `Response`, internamente se ejecuta un método que se encarga de transformar lo retornado en una `Response` (el método se llama `make_response()`).  \n",
    "\n",
    "  El objeto `Response` puede recibir los siguientes **parámetros opcionales**:\n",
    "  * `status`: Código de estado asociado a la respuesta. Si no se entrega se utiliza 200.\n",
    "  * `response`: Contenido del body asociado a la respuesta. Si no se entrega, significa que el body de la respuesta está vacío.\n",
    "  * `content_type`: Tipo de dato del body. Si no se entrega, lo definirá de forma automática.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "c5fdcb46",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Response [400]>"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta = requests.delete(\"http://localhost:4444/error/0\")\n",
    "respuesta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "f296c70d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Response [418]>"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "respuesta = requests.delete(\"http://localhost:4444/error/1\")\n",
    "respuesta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "8d749c94",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Response [200]>\n",
      "Podemos mandar textos\n"
     ]
    }
   ],
   "source": [
    "respuesta = requests.delete(\"http://localhost:4444/error/2\")\n",
    "print(respuesta)\n",
    "print(respuesta.text)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "7f4ece3a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Response [202]>\n",
      "{'mensaje': 'También JSONs'}\n"
     ]
    }
   ],
   "source": [
    "respuesta = requests.delete(\"http://localhost:4444/error/3\")\n",
    "print(respuesta)\n",
    "print(respuesta.json())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "d8f96831",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Servidor 2 detenido\n"
     ]
    }
   ],
   "source": [
    "servidor_2.shutdown()\n",
    "print(\"Servidor 2 detenido\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "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.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
