{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c00c3a39",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Ayudantía 8: Webservices 🌐"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "076b5163",
   "metadata": {
    "slideshow": {
     "slide_type": "skip"
    }
   },
   "source": [
    "## Ayudantes  👾\n",
    "\n",
    "- [Clemente Campos](https://github.com/mskdancers)\n",
    "- [Patricio Hinostroza](https://github.com/Dvckhv)\n",
    "- [Julio Huerta](https://github.com/julius)\n",
    "- [Carlos Olguin](https://github.com/CarlangaUC)\n",
    "- [Catalina Miranda](https://github.com/catalinamirandah)\n",
    "- [Felipe Vidal](https://github.com/fvidalf)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63a32f43",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## ¿Qué es un *webservice*?\n",
    "\n",
    "Para mejor entendimiento, *webservice* hace referencia al **conjunto de protocolos y estándares que permiten la comunicación cliente-servidor mediante la web**, es decir, las aplicaciones que permiten comunicar diversas máquinas mediante la web."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "85ebf917",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## ¿Qué es una API?\n",
    "\n",
    "Una API (*Application Programming Interface*), es vista como el **conjunto de funciones ofrecidas** (es decir, visibles) por un servicio para ser usadas por otros programas. \n",
    "\n",
    "Haciendo el símil con *networking*, desde el punto de vista del cliente, el servidor al que nos intentamos conectar contendría una API, pues este recibe peticiones y posee funciones destinadas a entregarnos resultados como respuesta. Más gráficamente..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6943f0ba",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "<img width=800 src=\"img/example.png\"> <br>\n",
    "<sup>Para mas informacion: https://medium.com/geekculture/a-beginners-guide-to-apis-9aa7b1b2e172</sup>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27bbe6b2",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## HTTP\n",
    "\n",
    "En este curso utilizaremos (o consumiremos) *APIs* y *Webservices* en Python mediante la libreria **requests**, que requiere el uso del protocolo HTTP. Este contiene diversos métodos/códigos para emplear el patrón **request-response** (solicitudes-respuestas), siendo el cliente el cual realiza solicitudes al servidor y este entrega una respuesta. Un ejemplo de lo planteado sería:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "a782208f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Solicitud realizada con status: 200\n",
      "Solicitud realizada con status: 404\n"
     ]
    }
   ],
   "source": [
    "import requests\n",
    "\n",
    "url = \"https://github.com/IIC2233/Syllabus\"\n",
    "response = requests.get(url)\n",
    "print(f'Solicitud realizada con status: {response.status_code}')\n",
    "\n",
    "url_erronea = \"https://github.com/IIC2233/Penca!\"\n",
    "response = requests.get(url_erronea)\n",
    "print(f'Solicitud realizada con status: {response.status_code}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1a72beaa",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "En este caso notamos que para la primera consulta nos arrojó el código **200** significando que fue realizada correctamente, mientras que la segunda consulta fue con **404** significando que no se encontró el contenido solicitado."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e123bb2b",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Métodos de consulta\n",
    "\n",
    "HTTP define métodos (caracterizados por un verbo) para ejecutar varios tipos de consulta, con diferentes objetivos. Los principales son los siguientes:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e7a82dd",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### GET\n",
    "\n",
    "Recupera u obtiene un recurso en el servidor. No hace cambios."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "48550c63",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'activity': 'Go on a long drive with no music', 'type': 'relaxation', 'participants': 1, 'price': 0.1, 'link': '', 'key': '4290333', 'accessibility': 0.2}\n"
     ]
    }
   ],
   "source": [
    "url = \"http://www.boredapi.com/api/activity\"\n",
    "response_get = requests.get(url)\n",
    "\n",
    "print(response_get.json())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0659c180",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### POST\n",
    "\n",
    "Crea un recurso en el servidor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "7bd23571",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'title': 'Plan súper original para pasar el rato', 'body': 'Go on a long drive with no music', 'link': '', 'id': 101}\n"
     ]
    }
   ],
   "source": [
    "url = \"https://jsonplaceholder.typicode.com/posts\"\n",
    "data = {\n",
    "    \"title\": \"Plan súper original para pasar el rato\",\n",
    "    \"body\": response_get.json()[\"activity\"],\n",
    "    \"link\": response_get.json()[\"link\"]\n",
    "}\n",
    "response_post = requests.post(url, data=data)\n",
    "\n",
    "print(response_post.json())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ed0768f",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### PATCH\n",
    "\n",
    "Actualiza parcialmente un recurso existente en el servidor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5f853f98",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "200\n"
     ]
    }
   ],
   "source": [
    "url = \"https://pythonexamples.org/\"\n",
    "data = {\n",
    "    \"some data\": \"just an example\",\n",
    "    \"more data\": \"literally anything else\"\n",
    "}\n",
    "\n",
    "response_patch = requests.patch(url, data=data)\n",
    "\n",
    "print(response_patch.status_code)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c456650",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### DELETE\n",
    "\n",
    "Elimina un recurso del servidor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "8da6af75",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "200\n"
     ]
    }
   ],
   "source": [
    "url = \"https://jsonplaceholder.typicode.com/photos/\"\n",
    "\n",
    "# Supongamos que borraremos algún recurso con id = 1\n",
    "_id = 1\n",
    "\n",
    "response_delete = requests.delete(url + f\"{_id}\")\n",
    "\n",
    "print(response_delete.status_code)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e44d176",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "En algunos casos, vamos a necesitar ciertos permisos para poder crear, actualizar o borrar recursos de algún servidor. En el siguiente ejemplo vamos a ver cómo podemos manejar esto.\n",
    "\n",
    "## Ejemplo: API de github\n",
    "\n",
    "En este ejemplo vamos a crear una issue en nuestro repositorio de github usando la API de github. Para hacer esto le entregaremos un diccionario mediante el parámetro `data`, que contendrá el título y el cuerpo de nuestra issue. Además, vamos a necesitar un token, que podemos generar en [esta página](https://github.com/settings/tokens/new). Este token debe tener al menos el siguiente permiso: _Full control of private repositories_, y se lo enviaremos a la API con el parámetro `headers`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24ba6eee",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json, requests\n",
    "\n",
    "github_repo = ''\n",
    "token = \"\"\n",
    "\n",
    "body = {\n",
    "    # Completar con lo que quieran!\n",
    "    'title': \"\",\n",
    "    'body': \"\"\n",
    "}\n",
    "\n",
    "my_headers = {\n",
    "    'Authorization': 'token ' + token,\n",
    "    'Accept': 'application/vnd.github.v3+json'\n",
    "}\n",
    "\n",
    "url = f\"https://api.github.com/repos/IIC2233/{github_repo}/issues\"\n",
    "\n",
    "response = requests.post(url, data=json.dumps(body), headers=my_headers)\n",
    "print(response.status_code)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d34c45cb",
   "metadata": {},
   "source": [
    "## APInterests\n",
    "\n",
    "¡Estamos desarrollando una aplicación para conectar personas, Interests! Para eso, tenemos nuestra propia base de datos (una lista) que mediante complejos algoritmos (un filtro) permite que un usuario conozca a otras personas con intereses similares, sean pasatiempos, gustos, actividades, ¡lo que sea que quieran compartir!\n",
    "\n",
    "Por ahora estamos definiendo las funcionalidades de la API, y necesitamos que alguien las pruebe para ver que todo esté en orden. Para eso, están disponibles los siguientes *endpoints*:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9556fd24",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Endpoints disponibles\n",
    "\n",
    "El servidor implementado en server.py cuenta con:\n",
    "\n",
    "#### GET: /users\n",
    "\n",
    "Retorna todos los usuarios registrados mediante el campo \"users\" de la respuesta json.\n",
    "\n",
    "#### GET: /user/{username}\n",
    "\n",
    "Retorna un único usuario dado el username proporcionado en el path. Este es retornado mediante el campo \"user\" de la respuesta json.\n",
    "En caso de no encontrarse, se retornará un *status_code* 404, y en la respuesta json, el campo \"message\" será \"Usuario no encontrado :(\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e6d1f7a",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "#### POST: /register\n",
    "\n",
    "Registra un usuario en la base de datos. Como parámetro `json` debe recibir los campos propios del usuario en forma de diccionario. Esto es:\n",
    "```\n",
    "{\n",
    "    \"nombre\": <nombre>,\n",
    "    \"username\": <username>\n",
    "    \"edad\": <edad>,\n",
    "    \"intereses\": [<interes_1>, <interes_2>, ..., <interes_n>],\n",
    "    \"dato_freak\": <dato_freak>\n",
    "}\n",
    "```\n",
    "\n",
    "En caso de realizarse exitosamente, el usuario estará disponible mediante el campo \"user\" de la respuesta json. Además, **se le asignará un id único**. En caso contrario, se retornará un *status_code* 400 y el campo \"message\" de la respuesta json será \"Error al registrar usuario\"."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18cb7888",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "#### GET: /group/{interes}\n",
    "\n",
    "Retorna un grupo de a lo más 3 usuarios mediante el campo \"users\" de la respuesta json. Los usuarios comparten el interés entregado en el path. En caso de no existir ningún usuario con ese interés, retornará un *status code* 404 y el campo \"message\" será \"No hay usuarios registrados con ese interés :(\".\n",
    "\n",
    "#### PATCH: /user/{id}\n",
    "\n",
    "Actualiza el campo \"intereses\" de un usuario dado mediante el id entregado en el path. La nueva lista de intereses se debe recibir mediante el parámetro json de la siguiente forma:\n",
    "```\n",
    "{\n",
    "    \"intereses\": [<interes_1>, <interes_2>, ..., <interes_n>]\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de64849c",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Lo que debes implementar\n",
    "\n",
    "En `client.py` te encontrarás con un flujo de programa que muestra un menú sencillo por consola. Deberás completar cada una de las opciones, asociadas a las siguientes tareas:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1a489752",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "#### 1. Obtener todos los usuarios\n",
    "Se debe realizar la request correspondiente, e imprimir cada usuario recibido por la API a la consola."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27f779c0",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "#### 2. Registrar un usuario\n",
    "Se debe recibir por consola un *str* 'nombre', un *str* 'username', un *int* 'edad', un *str* 'intereses' y un *str* 'dato_freak'. El *str* 'intereses' deberá corresponder a valores separables por comas, por ejemplo \"jardineria,pasear por las tardes,tomar cafe\". \n",
    "\n",
    "Luego, se debe realizar la request correspondiente. En caso de que esta sea exitosa, se debe imprimir el usuario guardado (retornado por la API)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2a28e7d",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "#### 3. Obtener un grupo aleatorio por interés\n",
    "Se debe recibir por consola un *str* 'interes'. Luego, se debe realizar la request correspondiente. En caso de que sea exitosa, se deberá imprimir cada uno de los usuario retornados por la API. En caso contrario, se deberá imprimir el *status code* resultante."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e9ab88d",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "#### 4. Actualizar los intereses de un usuario dado\n",
    "Se debe recibir por consola un *str* 'username' y un *str* 'intereses' del mismo formato que el usado al registrar. Luego, se debe realizar la request correspondiente. En caso de que esta sea exitosa, se debe imprimir el usuario actualizado (retornado por la API)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c26c54d6",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "#### 5. Registrar todos los usuarios en users.csv\n",
    "Se debe leer y procesar el archivo users.csv, para luego realizar las requests correspondientes para registrar a cada usuario.\n",
    "\n",
    "\n",
    "**Además**, para toda request deberás imprimir el campo \"message\" de la respuesta json, independientemente de si esta fue exitosa o no."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "809118da",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Para probar tus requests, puedes ejecutar el archivo server.py (también en la carpeta APInterests) en una terminal, y desde otra, ejecutar client.py"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Slideshow",
  "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.10.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
