ZOPE: Extender "ExtImage" para añadir un "hook" al crear una imagen "preview"

ZOPE es, posiblemente, el abuelo de los servidores de aplicaciones escritos en Python. Muchas y buenas ideas han salido de él. Tuvo su época dorada en la primera mitad los 2000. Hoy en día sigue en mantenimiento, aunque moviéndose rápidamente hacia la irrelevancia, salvo por excepciones honrosas como Plone.

Pero el legado pionero de ZOPE sigue vivo. Productos como ZODB o zc.buildout son utilizados a diario por millones. Frameworks como Piramid se basan en los principios de diseño de ZOPE.

Aunque ZOPE sigue en mantenimiento, está desapareciendo poco a poco y, ciertamente, todo el tiempo invertido en él es tiempo desperdiciado. Lamentablemente, mi web principal se basa en ZOPE. He invertido casi 20 años en el proyecto, y migrar a cualquier otra cosa es costoso y arriesgado.

Por tanto, no recomiendo ZOPE a nadie con un proyecto nuevo por su empinadísima curva de aprendizaje y su cuota de mercado en extinción. Pero yo soy perro viejo. ¡Qué se le va a hacer!.

Así que cada vez que me planteo una mejora en mi página web tengo que valorar siempre si invertir tiempo en mi instalación ZOPE o morder la bala por fin y migrar a algo hipotéticamente mejor pero que en realidad no necesito.

La situación se ha vuelto a repetir una vez más recientemente: Firma de imágenes. ¿Ampliar ZOPE o migrar toda mi infraestructura a un hipotético Edén?

Los álbumes fotográficos de mi web residen en ZOPE y empleo el producto ExtFile para reducir la carga en la ZODB y evitarle el manejo de muchos objetos de gran tamaño. Esta extensión de ZOPE permite almacenar objetos voluminosos en el propio sistema de ficheros, fuera de la ZODB. Útil porque una ZODB normal y corriente es un archivo "append only" y manejar, hacer copias de seguridad, compactar, etc., un documento de varias docenas de gigabytes no es plato de gusto para nadie.

ExtFile implementa un objeto ExtImage, que es lo que empleo en mis álbumes fotográficos.

Lamentablemente la última versión de ExtFile se publicó en 2007. Está abandonado. Tanto es así que hay que parchearlo para que funcione en las versiones actuales de ZOPE. Un desastre, vaya.

Nuevamente tengo que decidir si ampliar ZOPE o invertir mi tiempo migrando... y arriesgándome... Decisiones, siempre decisiones...

ExtFile o más apropiadamente, el objeto ExtImage, implementa algunas funcionalidades interesantes. Por ejemplo, puede generar una imagen "preview" y puede limitar el acceso a la imagen de alta resolución a ciertos roles o usuarios. Yo uso ambas funcionalidades en mi web.

He hecho los siguientes cambios en el código del producto ExtFile:

--- ExtImage.py.OLD     2014-07-27 15:09:39.448939152 +0200
+++ ExtImage.py 2014-07-27 22:06:33.744781082 +0200
@@ -530,16 +530,26 @@
         else:
             imfile = self._get_fsname(from_filename)
             if imfile:
+                def makePreview(im, maxx, maxy, ratio) :
+                    filter = Image.BICUBIC
+                    if hasattr(Image, 'ANTIALIAS'): # PIL 1.1.3
+                        filter = Image.ANTIALIAS
+                    if ratio:               # keep aspect-ratio
+                        im.thumbnail((maxx,maxy), filter)
+                    else:                   # distort to fixed size
+                        im = im.resize((maxx,maxy), filter)
+                    return im
+
                 im = Image.open(imfile)
                 if im.mode!='RGB':
                     im = im.convert('RGB')
-                filter = Image.BICUBIC
-                if hasattr(Image, 'ANTIALIAS'): # PIL 1.1.3
-                    filter = Image.ANTIALIAS
-                if ratio:               # keep aspect-ratio
-                    im.thumbnail((maxx,maxy), filter)
-                else:                   # distort to fixed size
-                    im = im.resize((maxx,maxy), filter)
+
+                preview = getattr(self, 'ExtImagePREVIEW', None)
+                if preview is None :
+                    im = makePreview(im, maxx, maxy, ratio)
+                else :
+                    im = preview(im, maxx, maxy, ratio, makePreview)
+
                 umask = os.umask(REPOSITORY_UMASK)
                 outfile = self._temp_fsname(to_filename)
                 try:

Aquí implemento un "hook" en la generación de las imágenes "preview". Básicamente, en vez de generar la imagen "preview" directamente, se busca (usando los mecanismos de adquisición característicos de ZOPE) un objeto (típicamente, lo que se conoce como un "método externo") llamado ExtImagePREVIEW. Si no existe, no pasa nada. se genera la imagen "preview" de forma normal. Pero si encontramos (por adquisición ZOPE [1]) un objeto de nombre ExtImagePREVIEW lo invocaremos con varios parámetros y almacenaremos la imagen final generada como "preview".

Si implementamos ExtImagePREVIEW con un "método externo", podremos hacer lo que nos de la gana.

Los parámetros que recibe son la imagen original (como objeto PIL), la resolución deseada de la imagen "preview", un flag sobre si se desea mantener la proporción X/Y de la imagen original y una referencia a la función original generadora de la imagen "preview", para que no haya que duplicar código si no es necesario.

Por este tipo de flexibilidad es por lo que sigo usando ZOPE. Y con él seguiré mientras siga recibiendo actualizaciones de seguridad y estabilidad. Estoy mayor para cambiar gratuitamente aunque, como ya he comentado, no recomiendo que ningún proyecto nuevo se plantee usar ZOPE. Masoquismo, arqueología informática, ciertas dosis de asombro y sentido de la maravilla aparte, claro está...

[1]

Verdaderamente, la adquisición fue una de las grandes innovaciones de ZOPE, y también su talón de Aquiles a la hora de dominar sus sorpresas. Las posibilidades que abre son inmensas, pero también lo son la complejidad conceptual y los efectos inesperados. Tanto es así que los framework posteriores aprendieron de ello y o no manejan el concepto de adquisición o lo gestionan de forma controlada y explícita.

Como tantas otras innovaciones pioneras de ZOPE, la adquisición sigue viva como proyecto separado.

Este artículo de 2001 da una idea de sus ventajas y de sus complejidades.