Saltar al contenido
🐍 Curso de Python
/

Módulo 5 · Lección 6 de 30

Parámetros, cabeceras y cuerpos

El filtro que desaparece sin dejar rastro, el booleano que llega con mayúscula, y los cuatro cuerpos que se pueden mandar.

⏱ 14 min de lectura

Ya sabes hacer peticiones. Esta lección va de las tres cosas que se ponen dentro de ellas, y de los detalles que hacen que una petición aparentemente correcta no haga lo que crees.

Casi todos los fallos de esta lección tienen la misma forma: no dan error. La petición sale, el servidor contesta 200, y los datos son los que no son.

Los parámetros de la consulta#

requests los codifica bien, y conviene ver exactamente qué hace con cada tipo:

Salida en pantalla
{'q': 'a & b'}          ->  ?q=a+%26+b
{'q': 'Óscar Ñuño'}     ->  ?q=%C3%93scar+%C3%91u%C3%B1o
{'q': 'c++'}            ->  ?q=c%2B%2B
{'ruta': 'a/b'}         ->  ?ruta=a%2Fb
{'id': [1, 2, 3]}       ->  ?id=1&id=2&id=3
{'a': ''}               ->  ?a=
{'a': 1, 'b': None}     ->  ?a=1
{'activo': True}        ->  ?activo=True

Las cuatro primeras son lo esperado y están bien: espacios, ampersands, acentos, barras y signos de suma, todos escapados.

Las cuatro últimas son las que hay que mirar dos veces.

Una lista repite la clave. Es la convención más común, y no es la única: algunas APIs quieren ?id=1,2,3. Si los filtros múltiples no funcionan, es lo primero que hay que comprobar, porque los dos formatos devuelven 200.

Un None desaparece. Y esta es la trampa de la lección:

python
parametros = {'estado': estado, 'autor': autor, 'desde': desde}
respuesta = sesion.get(url, params=parametros)

Si autor vale None, ese filtro no se manda, y la API devuelve todo en lugar de nada. Con 200, sin aviso, y con muchos más resultados de los que esperabas.

El comportamiento en sí es bueno, porque es justo lo que quieres al construir filtros opcionales. Lo malo es cuando el None es un error tuyo y lo confundes con "sin filtro". Si un parámetro es obligatorio, compruébalo tú:

python
if autor is None:
        raise ValueError('Hace falta el autor')

Y ojo con la diferencia entre None y cadena vacía, que son cosas distintas: None quita el parámetro, '' lo manda vacío, y para muchas APIs "vacío" significa "los que no tienen valor". No son intercambiables.

Las cabeceras#

Se leen sin preocuparse de las mayúsculas, que es un detalle cómodo:

Salida en pantalla
r.headers['ETAG']  ==  r.headers['etag']  ==  r.headers['Etag']
tipo: CaseInsensitiveDict

Y una cabecera que el servidor mandó dos veces llega junta:

Salida en pantalla
X-Test: uno
X-Test: dos      ->   r.headers['X-Test']  ==  'uno, dos'

Eso es lo que dice HTTP que hay que hacer, y es lo correcto para casi todas. Es incorrecto para Set-Cookie, que se separa con comas por dentro, y por eso requests la trata aparte: para las cookies se usa r.cookies.

Las de la sesión y las de la petición se combinan:

Salida en pantalla
la sesión pone:   X-Base: de-la-sesion, Accept: application/json
la petición pone: X-Extra: de-la-peticion, Accept: text/plain

lo que llega:     X-Base: de-la-sesion
                  X-Extra: de-la-peticion
                  Accept: text/plain        <- gana la de la petición

Las de la petición se añaden a las de la sesión, y cuando coinciden, ganan las de la petición. Que es lo que uno quiere.

Las que se ponen en la sesión son las que valen para todas: autenticación, agente, formato aceptado. Las de la petición son para lo puntual: una condición de If-None-Match, un formato distinto para una llamada concreta.

Y una que se olvida: para borrar una cabecera de la sesión en una petición no vale ponerla vacía, hay que ponerla a None:

python
sesion.get(url, headers={'Authorization': None})    # la quita
sesion.get(url, headers={'Authorization': ''})      # la manda vacía, que no es lo mismo

Los cuatro cuerpos#

Esta es la parte donde más se equivoca la gente, porque los cuatro parecen intercambiables y no lo son. Medidos contra un servidor que hace eco:

Salida en pantalla
json={'a': 1}                   ->  application/json                     {"a": 1}
data={'a': 1}                   ->  application/x-www-form-urlencoded    form: {'a': '1'}
data='{"a":1}' + cabecera       ->  application/json                     {"a": 1}
files={...}                     ->  multipart/form-data; boundary=...    files + form

json= es lo normal para una API. Convierte, codifica en UTF-8 y pone la cabecera.

data= con un diccionario manda un formulario clásico. Fíjate en que el 1 llega como '1': en un formulario todo es texto, no hay tipos. Es lo que quieren las APIs antiguas y algunos puntos de autenticación, que suelen exigir precisamente este formato.

data= con texto o bytes manda el cuerpo tal cual, sin poner ninguna cabecera. Es lo que hace falta cuando necesitas controlar el byte exacto: para firmar la petición, para mandar un JSON con las claves en un orden concreto, o para un formato que no es JSON. Con este hay que acordarse de poner el Content-Type a mano.

files= manda varias partes, que es lo de las subidas de ficheros del módulo anterior. Se puede combinar con data= para mandar campos junto al fichero, y es lo que hay que hacer: los dos en la misma petición.

python
sesion.post(url, files={'adjunto': ('informe.pdf', contenido, 'application/pdf')},
                 data={'titulo': 'Informe de septiembre'})