Los peligros de volcar a "pickle" cualquier cosa
En Generar un email diario a partir de un feed RSS (Slashdot) y Mejoras a la hora de generar un email diario a partir de un feed RSS (Slashdot) describo un programa para enviar por email las novedades diarias de un feed RSS.
Aunque al programa le vendría bien una reescritura, todo fue bien hasta hace unos días, cuando el programa falló con un error indicando que no se podía recuperar el estado volcado con pickle debido a una dependencia (inesperada e inadvertida) con beautifulsoup4.
El error en sí era críptico y poco claro, pero ocurrió tras haber actualizado la biblioteca beautifulsoup4. Esto fue una pista crucial. Obviamente todo volvió a la normalidad tras instalar una versión previa y así siguió cumpliendo con su trabajo unos días hasta que encontré tiempo para echarle un vistazo. La actualización de beautifulsoup4 no era urgente y pude aplazar la investigación hasta que me vino bien.
Una vez metido en faena quedó claro que el problema era que la estructura interna de beautifulsoup4 había cambiado y los objetos serializados de la versión anterior de beautifulsoup4 no podían cargarse en la versión actual de la biblioteca. Esto no es ninguna sorpresa, es lo normal. La sorpresa es que estuviera serializando objetos beautifulsoup4 de forma inadvertida.
Estudiando el fichero pickle compruebo que cuando yo pensaba que estaba serializando una simple cadena de texto en realidad estaba volcando un objeto beautifulsoup4 complejo entero. El parche en sí fue trivial:
diff -r 5ba761fceba5 -r b39f15f2bb66 slashdot.py --- a/slashdot.py Fri May 14 00:10:25 2021 +0200 +++ b/slashdot.py Mon Apr 15 22:23:54 2024 +0200 @@ -152,6 +152,8 @@ for entry in feed.entries: summary = BeautifulSoup(entry["summary"], 'html.parser') summary = list(summary.children)[0] + # Para que sea una cadena normal y no un bs4.NavigableString + summary = str(summary) entradas.new(entry['id'], entry.updated_parsed, entry['title'], entry['link'], summary) elif feed.status == 304:
summary no es una simple cadena de texto sino un objeto bs4.NavigableString. Ni era consciente ni lo necesito, es una dependencia inadvertida. Podemos convertir summary a texto plano simplemente usando la función str(). No afecta a nada más del código.
La migración fue fácil:
- Parchear el código como se acaba de indicar.
- Instalar la versión previa de beautifulsoup4.
- El programa funcionará con normalidad, pero a medida que vayan pasando las horas las entradas nuevas del feed RSS se irán almacenando ya en el pickle como cadenas de texto plano.
- Con el funcionamiento normal del programa se irán purgando las entradas antiguas, que son objetos bs4.NavigableString. En un día o dos ya no quedará ninguna. Todas las nuevas serán ya cadenas de texto.
- Una vez en este punto, actualizamos beautifulsoup4 a la última versión y todo funcionará sin problemas.
Un efecto colateral, además de evitar una dependencia indeseada con las interioridades cambiantes de beautifulsoup4, es que ahora el fichero pickle ocupa menos de la mitad. No es que importe mucho pasar de 120 Kbytes a 30 o 40 Kbytes, pero es gratis...