🐍 Curso Python · Nivel N4 Avanzado

Bloque 7: Web y APIs

Python también vive en el «backend»: desde una app que consume APIs públicas con requests hasta un servidor web completo con Flask. Aquí conectas con lo que ya sabes de HTML, CSS y JavaScript: creas rutas, formularios, redirecciones y una API REST que devuelve JSON. El reto: una API de productos.

⏱ ≈ 17 horas 📚 5 lecciones ✍️ 15 ejercicios guiados 📝 Examen del bloque + API REST de productos
Lección 1 · Teoría

HTTP y la librería requests

1.1 · El lenguaje de la web

HTTP habla con verbos: GET (pedir datos), POST (enviar y crear), PUT/PATCH (actualizar) y DELETE (borrar). Toda web que consumes es una API. La respuesta trae un código de estado: 200 OK, 201 creado, 404 no encontrado, 500 error interno.

1.2 · Consumir una API con requests

instalación y primera petición
$ pip install requests
GET a una API pública
import requests

r = requests.get("https://api.github.com/users/octocat")
print(r.status_code)        # 200
datos = r.json()              # respuesta en dict
print(datos["login"])       # octocat
⚠️ Comprueba siempre r.status_code. Si es 200, procesa; si es 404 no hay datos. Para errores de red usa try/except requests.RequestException.
POST con datos JSON
payload = {"nombre": "Ana", "email": "ana@x.es"}
r = requests.post("https://api.ejemplo.com/usuarios", json=payload)
print(r.status_code)   # 201 si se crea bien
Lección 2 · Teoría

Flask: tu primer servidor

Flask es el micro-framework web más popular de Python: en 3 líneas tienes una web funcionando.

instalar flask
$ pip install flask
app.py — mínimo vital
from flask import Flask

app = Flask(__name__)

@app.route("/")
def inicio():
    return "¡Hola, mundo!"

if __name__ == "__main__":
    app.run(debug=True)

Ejecuta python app.py y abre http://127.0.0.1:5000. El decorador @app.route("/") mapea una URL a una función.

💡 debug=True recarga el servidor solo al guardar y muestra errores detallados. ¡Son solo para desarrollo!
Lección 3 · Teoría

Rutas dinámicas, formularios y redirecciones

3.1 · Rutas con parámetros

URL con variable
from flask import Flask

app = Flask(__name__)

@app.route("/usuario/<nombre>")
def ver_usuario(nombre):
    return f"Perfil de {nombre}"

@app.route("/numero/<int:n>")
def cuadrado(n):
    return f"El cuadrado es {n ** 2}"

3.2 · Leer datos enviados por el cliente

En Flask, request.args lee parámetros de la URL ?clave=valor y request.form los campos de un formulario POST:

query params y formularios
from flask import Flask, request, redirect

app = Flask(__name__)

@app.route("/buscar")
def buscar():
    texto = request.args.get("q", "")
    return f"Buscando: {texto}"

@app.route("/enviar", methods=["POST"])
def enviar():
    nombre = request.form["nombre"]
    return redirect(f"/usuario/{nombre}")

redirect(...) envía al navegador a otra ruta. Es el patrón típico tras guardar un formulario: POST → procesar → redirigir.

Lección 4 · Teoría

Tu propia API REST con JSON

jsonify convierte un dict en una respuesta JSON con el tipo de contenido correcto. Así montas una API REST: URLs por recurso y verbos HTTP por acción.

API REST de productos
from flask import Flask, jsonify, request

app = Flask(__name__)
productos = [
    {"id": 1, "nombre": "ratón", "precio": 12},
    {"id": 2, "nombre": "teclado", "precio": 45},
]

@app.route("/api/productos")
def listar():
    return jsonify(productos)

@app.route("/api/productos", methods=["POST"])
def crear():
    datos = request.get_json()
    nuevo = {"id": len(productos) + 1, **datos}
    productos.append(nuevo)
    return jsonify(nuevo), 201
⚠️ En las respuestas puedes devolver un segundo valor: el código HTTP, por ejemplo return jsonify(nuevo), 201. Guía: GET→200, creación→201, no existe→404, error del cliente→400.
Lección 5 · Teoría

Conectar Flask con tu frontend

Flask puede servir páginas completas con plantillas (más adelante, con «templates» de Jinja) o únicamente responder JSON. La conexión más natural con lo que ya sabes de JavaScript es: tu página llama a tu API con fetch() y pinta el resultado.

frontend ↔ API propia
from flask import Flask, jsonify, request

app = Flask(__name__)

tarefas = ["estudiar Python", "hacer ejercicios"]

@app.route("/api/tareas")
def listar_tareas():
    return jsonify(tarefas)

@app.route("/api/tareas", methods=["POST"])
def anadir_tarea():
    cuerpo = request.get_json()
    tarefas.append(cuerpo["texto"])
    return jsonify(tarefas), 201
fetch desde tu página
<script>
fetch('http://127.0.0.1:5000/api/tareas')
  .then(r => r.json())
  .then(tareas => console.log(tareas));
</script>
💡 Si abres la página por file:// y el servidor está en otra URL, el navegador bloqueará la petición (CORS). Lo más simple: sirve también tu HTML desde Flask o usa flask-cors en desarrollo.
🎉 Ya has cerrado el círculo con el resto de tu formación: el frontend (HTML/CSS/JS) que aprendiste al inicio se conecta ahora con el backend de Python. Faltan los detalles de producción: bloque 8.
Manos a la obra

✍️ Los 15 ejercicios del bloque

Crea la carpeta python-b7/. Los primeros ejercicios son scripts de cliente; a partir del 8 son aplicaciones Flask que ejecutas con python app.py.

Sección A · Consumiendo APIs (requests)

1 Mi primera petición

Con requests.get consulta https://api.github.com/users/octocat, muestra el estado y los campos login y bio.

Ver solución
import requests

r = requests.get("https://api.github.com/users/octocat")
print(r.status_code)
datos = r.json()
print(datos["login"])
print(datos["bio"])

2 Estado y manejo de errores

Consulta una URL inexistente (https://api.github.com/users/este-usuario-no-existe-123) y muestra, según el código, «OK», «No encontrado» o «Error». Envuelve la petición en try/except.

Ver solución
import requests

try:
    r = requests.get("https://api.github.com/users/este-usuario-no-existe-123")
    if r.status_code == 200:
        print("OK")
    elif r.status_code == 404:
        print("No encontrado")
    else:
        print(f"Error {r.status_code}")
except requests.RequestException as e:
    print("Error de red", e)

3 Serie de peticiones

Petición a la lista de repos de https://api.github.com/users/octocat/repos y muestra el nombre y el lenguaje de los 3 primeros repos.

Ver solución
import requests

r = requests.get("https://api.github.com/users/octocat/repos")
repos = r.json()
for repo in repos[:3]:
    print(repo["name"], repo.get("language"))

Sección B · Flask básico

4 Hola desde Flask

Crea app.py con una ruta / que devuelva «¡Hola, Flask!». Ejecútalo y compruébalo en el navegador.

Ver solución
from flask import Flask

app = Flask(__name__)

@app.route("/")
def inicio():
    return "¡Hola, Flask!"

if __name__ == "__main__":
    app.run(debug=True)

5 Dos rutas

Añade a app.py una ruta /sobre que devuelva un breve texto sobre ti, y /contacto con tu email falso.

Ver solución
from flask import Flask

app = Flask(__name__)

@app.route("/")
def inicio():
    return "Bienvenido"

@app.route("/sobre")
def sobre():
    return "Estudiante de Python en TécnicoWeb"

@app.route("/contacto")
def contacto():
    return "hola@ejemplo.es"

if __name__ == "__main__":
    app.run(debug=True)

6 Ruta dinámica con nombre

Añade /usuario/<nombre> que devuelva "Hola, {nombre}". Pruébalo con tu nombre en el navegador.

Ver solución
from flask import Flask

app = Flask(__name__)

@app.route("/usuario/<nombre>")
def saludo(nombre):
    return f"Hola, {nombre}"

if __name__ == "__main__":
    app.run(debug=True)

7 Doble dinámica con int

Añade /numero/<int:n> que muestre el número y su doble. Si escribes letras, Flask debe responder 404 (comportamiento esperado).

Ver solución
from flask import Flask

app = Flask(__name__)

@app.route("/numero/<int:n>")
def numero(n):
    return f"{n} por 2 es {n * 2}"

if __name__ == "__main__":
    app.run(debug=True)

8 Query string

Rende /buscar que lea request.args.get("q") y devuelva «Buscando: …» (con valor por defecto vacío). Prueba /buscar?q=python.

Ver solución
from flask import Flask, request

app = Flask(__name__)

@app.route("/buscar")
def buscar():
    q = request.args.get("q", "")
    return f"Buscando: {q}"

if __name__ == "__main__":
    app.run(debug=True)

9 POST y redirect

Ruta /guardar que acepte POST, lea request.form["nombre"] y redirija a /usuario/<nombre>. Pruébalo con curl o un formulario simple en HTML.

Ver solución
from flask import Flask, request, redirect

app = Flask(__name__)

@app.route("/guardar", methods=["POST"])
def guardar():
    nombre = request.form["nombre"]
    return redirect(f"/usuario/{nombre}")

@app.route("/usuario/<nombre>")
def ver(nombre):
    return f"Hola, {nombre}"

if __name__ == "__main__":
    app.run(debug=True)

Sección C · Tu propia API

10 jsonify básico

Ruta /api/saludo que con jsonify devuelva {"mensaje": "hola", "ok": true}. Compruébalo en el navegador (debe verse JSON).

Ver solución
from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/api/saludo")
def saludo():
    return jsonify({"mensaje": "hola", "ok": True})

11 API de tareas: listar

Con una lista global tareas = ["estudiar", "reposar"], crea /api/tareas (GET) que devuelva la lista con jsonify.

Ver solución
from flask import Flask, jsonify

app = Flask(__name__)
tareas = ["estudiar", "reposar"]

@app.route("/api/tareas")
def listar():
    return jsonify(tareas)

12 API de tareas: crear

Amplía la anterior con POST /api/tareas que lea el JSON con request.get_json() (campo texto), lo añada a la lista y devuelva 201. Pruébalo con la extensión «REST Client» de VS Code o con requests desde otro script.

Ver solución
from flask import Flask, jsonify, request

app = Flask(__name__)
tareas = ["estudiar", "reposar"]

@app.route("/api/tareas")
def listar():
    return jsonify(tareas)

@app.route("/api/tareas", methods=["POST"])
def crear():
    cuerpo = request.get_json()
    tareas.append(cuerpo["texto"])
    return jsonify(tareas), 201

13 Cliente de tu propia API

Con Flask corriendo, crea cliente.py que con requests liste las tareas y luego añada una «hacer el examen» con POST. Muestra el resultado.

Ver solución
import requests

base = "http://127.0.0.1:5000/api/tareas"

print(requests.get(base).json())
r = requests.post(base, json={"texto": "hacer el examen"})
print(r.status_code, r.json())

14 Búsqueda en la API

Añade al servidor de tareas una ruta /api/tareas/buscar que lea ?texto= y devuelva con un filtro (list comprehension) solo las tareas que lo contengan.

Ver solución
from flask import Flask, jsonify, request

app = Flask(__name__)
tareas = ["estudiar python", "reposar", "hacer el examen"]

@app.route("/api/tareas/buscar")
def buscar():
    texto = request.args.get("texto", "").lower()
    coinciden = [t for t in tareas if texto in t.lower()]
    return jsonify(coinciden)

15 🏆 Proyecto integrador: «API REST de productos»

Crea api_productos.py: una API REST completa con Flask. Requisitos:

  • Lista global de productos: {"id", "nombre", "precio", "categoria"} (popúlala con 3+).
  • GET /api/productos → lista completa.
  • POST /api/productos → crea con request.get_json() y devuelve 201 y el producto creado.
  • GET /api/productos/<int:pid> → uno concreto o 404 con jsonify({"error": "no encontrado"}), 404.
  • Filtro opcional GET /api/productos?categoria=….
  • DELETE /api/productos/<int:pid> → borra y responde 200.
  • Cliente cliente.py con requests que liste, cree y borre un producto.
  • Docstring en las funciones y comentarios de la estructura.
Ver una solución posible
from flask import Flask, jsonify, request

app = Flask(__name__)
productos = [
    {"id": 1, "nombre": "ratón", "precio": 12, "categoria": "periférico"},
    {"id": 2, "nombre": "monitor", "precio": 150, "categoria": "pantalla"},
    {"id": 3, "nombre": "teclado", "precio": 45, "categoria": "periférico"},
]

def siguiente_id():
    """Devuelve el id siguiente al último existente."""
    return max(p["id"] for p in productos) + 1

@app.route("/api/productos")
def listar():
    """Lista productos (opcional: filtro por ?categoria=...)."""
    categoria = request.args.get("categoria")
    if categoria:
        return jsonify([p for p in productos if p["categoria"] == categoria])
    return jsonify(productos)

@app.route("/api/productos", methods=["POST"])
def crear():
    """Crea un producto desde el JSON enviado."""
    datos = request.get_json()
    nuevo = {"id": siguiente_id(), **datos}
    productos.append(nuevo)
    return jsonify(nuevo), 201

@app.route("/api/productos/<int:pid>")
def detalle(pid):
    """Devuelve un producto por id o 404."""
    for p in productos:
        if p["id"] == pid:
            return jsonify(p)
    return jsonify({"error": "no encontrado"}), 404

@app.route("/api/productos/<int:pid>", methods=["DELETE"])
def borrar(pid):
    """Borra un producto por id."""
    global productos
    productos = [p for p in productos if p["id"] != pid]
    return jsonify({"ok": True})

if __name__ == "__main__":
    app.run(debug=True)
Evaluación del bloque

📝 Examen del Bloque 7

Dos partes: un test teórico autoevaluable (mínimo 70 %) y un reto práctico de autocorrección con laboratorio.

Parte A · Test teórico (aprueba con 11/15)

1 · ¿Qué verbo HTTP pide datos sin efectos secundarios?
2 · ¿Qué significa el estado 404?
3 · ¿Cómo conviertes una respuesta JSON de requests a dict?
4 · Flask crea una app con:
5 · ¿Qué hace @app.route("/")?
6 · ¿Qué tipo de parámetro captura /producto/<int:pid>?
7 · ¿Qué devuelve request.args.get("q")?
8 · ¿Qué módulo importas para leer campos de un formulario POST?
9 · ¿Para qué sirve redirect(...) en Flask?
10 · ¿Qué hace jsonify en Flask?
11 · ¿Qué módulo de Flask lee el cuerpo JSON de un POST?
12 · ¿Qué devuelves para responder 201 en Flask?
13 · ¿Cómo se inicia normalmente una app Flask bajo __main__?
14 · ¿Qué comando instala Flask?
15 · ¿Qué hace con la API un script con requests.post?

Parte B · Reto «mini API»

Crea mini_api.py con Flask: una API mínima pero completa. Valida tú mismo/a estos criterios. Mínimo para aprobar: 6 de 8 casillas.

Cumplidos: 0 / 8

🎉 ¿Superaste las dos partes? Bloque 7 completado. Request y Flask ya no tienen secretos: consumes APIs y expones la tuya con JSON. Solo queda pulir el oficio definitivo en el bloque 8. 🐍🔥