Saltar a contenido

1.15 · Librerías externas

Objetivos. Al terminar este capítulo podrás: distinguir biblioteca estándar y paquete externo; justificar la elección de una dependencia; usar httpx2 como cliente HTTP y configuración desde el entorno; y reutilizar pandas y NumPy sin repetir su teoría.

Evidencia de logro. Compararás alternativas, documentarás la elección de una dependencia, consumirás una respuesta HTTP servida localmente y procesarás su resultado con las herramientas de datos del proyecto, dejando documentadas las decisiones y los paquetes opcionales.

Contexto y motivación

La biblioteca estándar cubre muchas tareas, pero una aplicación de IA suele necesitar clientes HTTP, validación, datos numéricos o un framework web. Una librería externa puede ahorrar trabajo, pero también añade versiones que deben declararse, comprobarse y mantenerse.

Una dependencia entra cuando resuelve una necesidad concreta y su coste está justificado. La declaración del proyecto se conserva en pyproject.toml; aquí nos centraremos en elegir una API adecuada y en comprobar su comportamiento.

Vocabulario

Término Significado
Paquete Distribución instalable desde un índice como PyPI
Dependencia Paquete que otro proyecto necesita
pyproject.toml Manifiesto con metadatos y dependencias directas
Dependencia directa Paquete que el proyecto declara y utiliza explícitamente
Dependencia transitiva Paquete instalado porque otro lo necesita
API Interfaz pública que una librería ofrece
Cliente HTTP Código que realiza peticiones a un servidor
Credencial Dato secreto que identifica o autoriza una petición

Prerrequisitos

1.1 · Introducción y entorno, 1.10 · Archivos y 1.14 · Biblioteca estándar. Para la parte de datos, consulta 1.4 · NumPy y 1.5 · Pandas del módulo de Machine Learning; aquí se usan pandas y NumPy, no se repite su fundamento.

1. Biblioteca estándar o paquete externo

Antes de instalar algo, formula la necesidad y compara:

Necesidad Primera opción Paquete externo cuando...
JSON, rutas, CSV Biblioteca estándar necesitas una integración específica
Petición HTTP urllib o un cliente como httpx2 quieres una API más cómoda y segura
Tabla de datos pandas el problema necesita sus operaciones tabulares
Cálculo vectorizado NumPy necesitas arrays y operaciones numéricas
Leer Excel csv para CSV el formato requiere openpyxl
HTML re solo para casos triviales necesitas analizar un documento: beautifulsoup4
Imagen Biblioteca estándar no basta usa Pillow u otra librería especializada

No se deben instalar todas las alternativas «por si acaso». Una dependencia entra cuando resuelve una necesidad concreta y su coste está justificado.

2. Elegir y declarar una dependencia

Antes de utilizar una librería externa, revisa su nombre de distribución, API pública, compatibilidad con la versión de Python del proyecto, licencia, actividad y avisos de seguridad. Una dependencia directa debe quedar declarada en pyproject.toml junto con una restricción de versión razonable:

[project]
dependencies = [
    "httpx2>=2.12.0",
    "python-dotenv>=1.2.3",
]

En tu proyecto no editas esa lista a mano: la escribe uv add, que además resuelve las versiones, actualiza uv.lock e instala los paquetes en .venv. Para seguir este capítulo en tu proyecto de la unidad:

uv add httpx2 python-dotenv pandas numpy

Efecto observable: las cuatro dependencias aparecen en pyproject.toml y uv run python -c "import httpx2, dotenv, pandas, numpy" termina sin errores. El detalle de uv add, uv.lock y uv sync está en la guía de uv. Aquí interesa sobre todo justificar qué paquete resuelve el problema y qué coste de mantenimiento añade.

Por qué httpx2 y no otro cliente

Durante años, el cliente HTTP de referencia en Python fue requests, y más tarde httpx (que añadía async y HTTP/2). En 2026, httpx entró en modo de mantenimiento con actividad limitada, y Pydantic recogió el testigo creando httpx2: la misma API, mantenida activamente, con actualizaciones de seguridad garantizadas para una librería que está en el camino crítico de muchísimos sistemas en producción.

La lección de dependencias que este caso enseña es doble: una librería popular puede agotarse (su autor se mueve a otra cosa), y lo que salva un ecosistema es que otro equipo con credenciales la continúe. Al elegir una dependencia, la actividad del proyecto pesa tanto como su API: un cliente perfecto sin mantenimiento es una deuda futura.

3. Cliente HTTP sin depender de una red real

Un cliente HTTP debe definir timeout, comprobar el estado y tratar la respuesta como datos no confiables. Para aprenderlo sin Internet levantamos un servidor local que responde con JSON y hacemos la petición con httpx2.

Qué parte hay que entender

La función servidor_json es infraestructura para poder practicar sin Internet: arranca en segundo plano un servidor web mínimo que siempre responde con el JSON que le indiques. Cópiala tal cual; no necesitas entender cómo está hecha (usa hilos y el módulo http.server, que no son materia de esta unidad). Lo importante del ejemplo son las cuatro líneas finales, las del bloque with httpx2.Client().

import json
from contextlib import contextmanager
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread

import httpx2


@contextmanager
def servidor_json(cuerpo, estado=200):
    class Manejador(BaseHTTPRequestHandler):
        def do_GET(self):
            contenido = json.dumps(cuerpo).encode("utf-8")
            self.send_response(estado)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(contenido)))
            self.end_headers()
            self.wfile.write(contenido)

        def log_message(self, formato, *argumentos):
            pass

    servidor = ThreadingHTTPServer(("127.0.0.1", 0), Manejador)
    hilo = Thread(target=servidor.serve_forever, daemon=True)
    hilo.start()
    try:
        yield f"http://127.0.0.1:{servidor.server_port}"
    finally:
        servidor.shutdown()
        servidor.server_close()


with servidor_json({"modelo": "demo", "estado": "disponible"}) as base_url:
    with httpx2.Client() as cliente:
        respuesta = cliente.get(f"{base_url}/modelo", timeout=5.0)
        respuesta.raise_for_status()
        datos = respuesta.json()

print(datos["modelo"], datos["estado"])

Salida esperada:

demo disponible

Lee esas cuatro líneas en orden:

  1. httpx2.Client() crea un cliente HTTP; con with, sus conexiones se cierran al terminar, como un fichero.
  2. cliente.get(url, timeout=5.0) pide la URL y espera como mucho 5 segundos; sin timeout, un servidor que no responde bloquearía el programa.
  3. respuesta.raise_for_status() lanza una excepción si el servidor respondió con un código de error (4xx o 5xx), en lugar de seguir con datos vacíos.
  4. respuesta.json() convierte el cuerpo JSON en diccionarios y listas de Python.

El servidor solo escucha en 127.0.0.1 (tu propio equipo), por lo que la salida es determinista y no depende de Internet.

En una integración real añadirías reintentos limitados para errores transitorios, registrarías el identificador de la petición y nunca imprimirías una clave secreta. Los reintentos y la autenticación se amplían en la UD2.

4. Configuración sin secretos en el código

Un fichero .env local puede contener configuración que no debe versionarse:

ASISTENTE_MODELO=demo
ASISTENTE_TIMEOUT=5

Con python-dotenv se puede leer sin convertirlo en una constante del código:

from pathlib import Path
from dotenv import dotenv_values

ruta_env = Path(".env_ejemplo")
ruta_env.write_text("ASISTENTE_MODELO=demo\nASISTENTE_TIMEOUT=5\n", encoding="utf-8")

configuracion = dotenv_values(ruta_env)
modelo = configuracion.get("ASISTENTE_MODELO", "local")
timeout = float(configuracion.get("ASISTENTE_TIMEOUT", "5"))

print(modelo, timeout)
ruta_env.unlink(missing_ok=True)

Salida esperada:

demo 5.0

El bloque crea un .env_ejemplo temporal para que la salida no dependa de un fichero previo, y lo elimina al terminar. Añade .env a .gitignore y entrega un .env.example sin valores secretos.

En producción, el proveedor de despliegue debe inyectar las variables; no se suben claves a GitHub ni se escriben en el cuaderno.

Dependencia opcional

python-dotenv facilita el desarrollo local, pero el principio no depende de ella: Python también ofrece os.environ. No confundas «se puede leer un .env» con «es seguro publicar su contenido».

5. Usar pandas y NumPy sin repetir el módulo de ML

En este módulo los usamos como piezas de una aplicación. El análisis profundo de tablas, arrays, dtypes y rendimiento pertenece a Machine Learning.

import numpy as np
import pandas as pd

respuestas = [
    {"usuario": "ana", "puntuacion": 0.8},
    {"usuario": "luis", "puntuacion": 0.6},
]
tabla = pd.DataFrame(respuestas)
tabla["puntuacion_porcentaje"] = np.round(tabla["puntuacion"] * 100, 1)

print(tabla[["usuario", "puntuacion_porcentaje"]].to_dict("records"))

Salida esperada:

[{'usuario': 'ana', 'puntuacion_porcentaje': 80.0}, {'usuario': 'luis', 'puntuacion_porcentaje': 60.0}]

La representación es explícita: el servicio entrega puntuaciones entre 0 y 1 y la interfaz necesita porcentajes. En pandas 3.x conviene comprobar dtypes y no basarse en conversiones implícitas accidentales; el módulo de ML desarrolla esas decisiones con más profundidad.

6. Elegir un paquete desde PyPI

Antes de añadir una distribución revisa:

  1. nombre exacto y API pública;
  2. versión de Python y dependencias compatibles;
  3. licencia y actividad del proyecto;
  4. documentación, historial de versiones y avisos de seguridad;
  5. si la funcionalidad puede resolverse con una dependencia ya instalada.

El nombre de la distribución y el nombre que se importa pueden ser distintos. Por ejemplo, se instala python-dotenv pero se importa dotenv. No copies el nombre del comando de instalación sin comprobar la documentación.

6.1 Catálogo de ampliaciones

Los cuatro primeros paquetes de la tabla son útiles, pero no hacen falta para esta unidad. Te los señalamos como mapa de elección: cuando en el curso o en tu trabajo aparezcan, sabrás para qué son y si te conviene instalarlos.

Paquete Caso de uso Requisito para esta unidad
requests HTTP sencillo No; se prioriza httpx2
beautifulsoup4 Parsear HTML No; ampliación
Pillow Transformar imágenes No; ampliación
openpyxl Libros Excel No; ampliación
httpx2 Cliente HTTP Sí para esta práctica
python-dotenv Configuración local Sí para esta práctica

La tabla evita que una persona principiante tenga que instalar cinco paquetes para resolver una sola tarea.

Aplicación práctica: pipeline local de una respuesta

Este flujo simula una petición, transforma el resultado y genera un registro listo para guardar:

# Se reutiliza `servidor_json`, definido en el ejemplo anterior.
import httpx2
import numpy as np
import pandas as pd


with servidor_json({"resultados": [0.8, 0.6, 0.9]}) as base_url:
    with httpx2.Client() as cliente:
        resultado = cliente.get(f"{base_url}/puntuaciones").raise_for_status()
        print("tipo de resultado:", type(resultado).__name__)

Salida esperada:

tipo de resultado: Response

En httpx2, raise_for_status() devuelve la propia respuesta cuando no hay error, así que se puede encadenar en la misma línea. Funciona, pero mezcla dos pasos (pedir y comprobar) y no todas las librerías se comportan igual. Para que cada etapa sea visible y el patrón sea fácil de trasladar a otras librerías, suele ser más claro conservar la respuesta en una variable:

with servidor_json({"resultados": [0.8, 0.6, 0.9]}) as base_url:
    with httpx2.Client() as cliente:
        respuesta = cliente.get(f"{base_url}/puntuaciones", timeout=5.0)
        respuesta.raise_for_status()
        tabla = pd.DataFrame({"puntuacion": respuesta.json()["resultados"]})
        tabla["porcentaje"] = np.round(tabla["puntuacion"] * 100, 1)

print(tabla.to_dict("records"))

Salida esperada:

[{'puntuacion': 0.8, 'porcentaje': 80.0}, {'puntuacion': 0.6, 'porcentaje': 60.0}, {'puntuacion': 0.9, 'porcentaje': 90.0}]

Las dos versiones funcionan. La segunda es la recomendada: conserva cada etapa (pedir, comprobar, convertir, transformar) y hace visible la transformación. Si un paso falla, el traceback señala exactamente cuál.

Errores frecuentes

  • Preparar un entorno distinto del proyecto y luego ejecutar con otro intérprete.
  • Usar una dependencia que no está declarada en pyproject.toml.
  • Usar una llamada HTTP real en una práctica que debería ser reproducible.
  • No definir timeout ni comprobar raise_for_status().
  • Subir .env, tokens o claves a GitHub.
  • Confundir paquete distribuido e importación (python-dotenv frente a dotenv).
  • Repetir la teoría de pandas o NumPy en lugar de explicar su papel en la aplicación.
  • Añadir una librería grande cuando la biblioteca estándar ya resuelve el caso.

Práctica de transferencia

  1. Compara httpx2 con otra alternativa y deja httpx2 y python-dotenv declarados en pyproject.toml; comprueba que el entorno del proyecto puede importarlos.
  2. Sustituye una petición real por un servidor local y diseña respuestas 200 y 404; explica cómo las distinguirías con raise_for_status().
  3. Crea .env.example, añade .env a .gitignore y lee una configuración sin imprimir ningún secreto.
  4. Convierte tres respuestas simuladas en una tabla pandas y calcula una columna derivada con NumPy.
  5. Escribe una nota de decisión: qué paquete elegiste, qué alternativa descartaste y qué coste de mantenimiento introduces.

Producto evaluable

Crea 11_dependencias.py como script o cuaderno marimo. Debe:

  1. Mostrar la declaración de dependencias en pyproject.toml y justificar la elección de httpx2 frente a una alternativa.
  2. Consumir una respuesta HTTP simulada con timeout y comprobación de estado.
  3. Leer una configuración de ejemplo sin incluir credenciales reales.
  4. Transformar la respuesta con pandas y NumPy, indicando qué conocimiento se reutiliza del módulo de ML.
  5. Incluir una tabla de dependencias directas, opcionales y de desarrollo.

Criterio de aceptación: otra persona puede preparar el entorno del proyecto y repetir la práctica sin Internet ni secretos; la entrega explica qué sucede cuando el servicio responde con error.

Formato de entrega: script o cuaderno marimo ejecutado, con salida esperada, interpretación y conclusión.

Resumen y referencia rápida

Tarea Comando o patrón
Declarar dependencia Entrada en pyproject.toml
Cliente con timeout httpx2.Client() y cliente.get(..., timeout=5.0)
Comprobar HTTP respuesta.raise_for_status()
Leer .env dotenv_values(".env")

Siguiente: 1.16 · Asincronía con asyncio.