<jupyter_start><jupyter_text>[![img/pythonista.png](img/pythonista.png)](https://www.pythonista.io)  *RESTful APIs*.  La arquitectura *RESTful* o *REST*.*REST* es el acrónimo de "Transferencia Representacional de Estado", la cual es una propuesta de arquitectura de servcios web basada en los métodos definidos para HTTP. A estos servicios también se les conoce como *RESTFul*.La arquitectura REST fue propuesta por primera vez en [la tesis doctoral](https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm) de Roy Fieding en el año 2000.REST es una arquitectura que resulta flexible y simple en comparación con otras propuestas y en vista de que no restringe el uso de algún formato de datos ni tampoco exige el apego a esquema predefinidos, puede ser ampliamante extendida.En este capítulo utilizaremos JSON como el formato por defecto para la comunicación de mensajes de datos.  Las interfaces de programación de aplicaciones (API).Una API permite definir las reglas y el modo en los que un usuario puede interactuar con un sistema mediante la construcción de expresiones con instrucciones y esquemas de datos específicos.Una API web es aquella que permite enviar instrucciones por medio del uso de URL (end point) y los datos que se envían a ésta.  Objetivos de este capítulo.Se creará una API que realizará operaciones de altas, bajas y cambios o CRUD (el acrónimo de "crear, leer, actualizar y eliminar" en inglés) en una base de datos rudimentaria.* Cada una de las operaciones serán definidas mapeando un método HTTP  a una función que realice las operaciones utilizando los datos enviados en la petición.* Los "end points" corresponden a una URL compuesta por una ruta fija desde ```/api/``` añadiendo un número que correspondería al campo *"Cuenta"* de un registro en la base de datos.* El resto de la información será enviada en formato JSON con los campos obligatorios *"Nombre"*, *"Primer Apellido"*, *"Carrera"*, *"Semestre"*, *"Promedio"* y *Al Corriente"*, y adicionalmente el campo opcional *"Segundo Apellido"* el cual en caso de no ser enviado será sustituido por una cadena de caracteres vacía en la base de datos. * Los campos deben de cumplir ciertas reglas y apegarse a la estructura descrita. De lo contrario, la operación no se realizaría.  Importación de módulos y datos.  El paquete *data*.El paquete data corresponde al directorio local [data/](data/), el cual contiene al archivo [*data/\_\_init\_\_.py*](data/__init__.py) con la siguiente información.``` python! /usr/bin/python3 La ruta en la que se encuentra la base de datos.ruta = 'data/alumnos.txt' Define los campos que conforman la estructura de un mensaje completo. orden = ('Nombre', 'Primer Apellido', 'Segundo Apellido', 'Carrera','Semestre', 'Promedio', 'Al Corriente') Indica el tipo de dato de cada campo en un regsitro de la base de datos, y si este es obligatorio (True).campos ={'Cuenta': (int, True), 'Nombre': (str, True), 'Primer Apellido': (str, True), 'Segundo Apellido': (str, False), 'Carrera': (str, True), 'Semestre': (int, True), 'Promedio': (float, True), 'Al Corriente': (bool, True)} Listado de las cadenas de caracteres que deben aceptarse en el campo "Carreras". carreras = ("Sistemas", "Derecho", "Actuaría", "Arquitectura", "Administración")```<jupyter_code>from flask import Flask, jsonify, request, abort
from json import loads
from data import ruta, campos, orden, carreras<jupyter_output><empty_output><jupyter_text>Definición de funciones.  Funciones de gestión de la base de datos.En este caso la base de datos no es otra cosa más que un archivo de texto que representa a un objeto de tipo *list* de Python. La base de datos puede ser consultada en [data/alumnos.txt](data/alumnos.txt).   Función de carga de datos.<jupyter_code>def carga_base(ruta):
    with open(ruta, 'tr') as base:
        return eval(base.read())<jupyter_output><empty_output><jupyter_text>Función de escritura de datos.<jupyter_code>def escribe_base(lista ,ruta):
    with open(ruta, 'wt') as base:
            base.write(str(lista))<jupyter_output><empty_output><jupyter_text>Función de búsqueda en la base de datos.* Busca dentro del campo *'Cuenta'* de cada elemento de *base* al número entero correspondiente al argumento de *cuenta*.* En caso de encontrar una coincidencia, regresa al elemento.* En caso de no encontrar coincidencia regresa *False*.<jupyter_code>def busca_base(cuenta, base):
    for alumno in base:
        try:
            if alumno['Cuenta'] == int(cuenta):
                return alumno
        except:
            return False
    return False<jupyter_output><empty_output><jupyter_text>Funciones de validación de datos.  Función que valida el tipo de dato.<jupyter_code>def es_tipo(dato, tipo):
    if tipo == str:
        return True
    else:
        try: 
            return tipo(dato) is dato
        except:
            return False<jupyter_output><empty_output><jupyter_text>Función que valida las reglas de los datos.* Los campos *'Nombre"* y *'Primer Apellido'* no deben de ser una cadena de caracteres vacía.* El campo 'Semestre' debe de ser un entero mayor a 1.* La cadena de caracteres del campo 'Carrera' debe de estar dentro de las cadenas listadas en *datos.carrera*.* El campo promedio debe de ser un número entre 0 y 10.<jupyter_code>def reglas(dato, campo):
    if campo == "Carrera" and dato not in carreras:
        return False
    elif campo == "Semestre" and dato < 1:
        return False
    elif campo == "Promedio" and (dato < 0 or dato > 10):
        return False
    elif (campo in ("Nombre", "Primer Apellido") and (dato == "")):
        return False
    else:
        return True<jupyter_output><empty_output><jupyter_text>Función de validación de tipos y reglas.<jupyter_code>def valida(dato, campo):
    return es_tipo(dato, campos[campo][0]) and reglas(dato, campo)<jupyter_output><empty_output><jupyter_text>Función que valida que el mensaje contiene todos los campos obligatorios.<jupyter_code>def recurso_completo(base, ruta, cuenta, peticion):
    try:
        candidato = {'Cuenta': int(cuenta)}
        peticion = loads(peticion)
        if (set(peticion)).issubset(set(orden)):                    
            for campo in orden:
                if not campos[campo][1] and campo not in peticion:
                    candidato[campo] = ''
                elif valida(peticion[campo], campo):
                    candidato[campo] = peticion[campo]      
                else:
                    abort(400)
        else:
            abort(400)
    except:
        abort(400)
    base.append(candidato)
    escribe_base(base, ruta)
    return jsonify(candidato)<jupyter_output><empty_output><jupyter_text>Código del servidor.* El servidor correrá en [localhost:5000/api/](localhost:5000/api/). Si se accede a la raíz, se desplegará un listado de todos los alumnos en formato JSON.* El servidor soporta los métodos:    * **GET**: para obtener la información de un alumno por su número de cuenta.    * **POST**: para crear un registro nuevo.    * **PUT**: para sustituir por completo un registro existente.    * **PATCH**: para modificar ciertos datos de un registro existente.    * **DELETE**: para eliminar un registro existente.<jupyter_code>app = Flask(__name__)


@app.route('/api/', methods=['GET'])
def index():
    with open(ruta, 'tr') as base:    
        return jsonify(eval(base.read()))

    
@app.route('/api/<cuenta>', methods=['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
def api(cuenta):
    
    if request.method == 'GET':
        base = carga_base(ruta)
        alumno = busca_base(cuenta, base)
        if alumno:
            return jsonify(alumno)
        else:
            abort(404)
            
    if request.method == 'DELETE':               
        base = carga_base(ruta)
        alumno = busca_base(cuenta, base)
        if alumno:
            base.remove(alumno)
            escribe_base(base, ruta)
            return jsonify(alumno)
        else:
            abort(404)
        
    if request.method == 'POST':
        base = carga_base(ruta)
        alumno = busca_base(cuenta, base)
        if alumno:
            abort(409)
        else:
            return recurso_completo(base, ruta, cuenta, request.data)
            
    if request.method == 'PUT':
        base = carga_base(ruta)
        alumno = busca_base(cuenta, base)
        if not alumno:
            abort(404)
        else:
            base.remove(alumno)
            return recurso_completo(base, ruta, cuenta, request.data)

    if request.method == 'PATCH':               
        base = carga_base(ruta)
        alumno = busca_base(cuenta, base)
        if not alumno:
            abort(404)
        else:
            indice = base.index(alumno)
            try:
                peticion = loads(request.data)
                if (set(peticion)).issubset(set(orden)):
                    for campo in peticion:
                        dato = peticion[campo]
                        if valida(dato, campo):
                            alumno[campo] = dato
                        else:
                            abort(400)
                else:
                    abort(400)
            except:
                abort(400)
            base[indice] = alumno
            escribe_base(base, ruta)
            return jsonify(alumno)
app.run('0.0.0.0')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09389721627408994, 'fraction_numerical': 0.006423982869379015, 'mean_word_length': 2.9630886720407297, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 39, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '348294', 'license': 'MIT', 'n_tokens_mistral': 3050, 'n_tokens_neox': 3016, 'n_words': 1010, 'path': '16_restful_apis.ipynb', 'repo_name': 'davidrodord56/py221', 'size': '9340'}