[pyar] si se ve como un pato y suena como un pato...

Nicolas Echaniz nico en rakar.com
Mie Oct 6 14:02:58 ART 2010


Hace un par de días, con Diego Mascialino, trabajando en un problema 
específico de Cyclope[0] nos cruzamos con algo que nos pareció interesante 
compartir por acá.
Nuestro caso es referido a deployment de Django, pero el problema no es 
específico de Django.

Lo que necesitamos resolver es el desafío de tener que montar miles de sitios 
(casi todos de muy bajo tráfico) con Cyclope, sin tener que instalarte al 
server decenas de gigas de RAM (tiene 8).
Hicimos muchas pruebas de deployment  con mod_wsgi, fastcgi, gunicorn+nginx, 
spawning, etc. En cualquier caso, no logramos que cada proyecto de django 
(cada instancia de Cyclope) ocupara menos de 20Mb en RAM; salvo usando CGI :)


Empezamos a pensar entonces en servir múltiples proyectos desde uno solo.
El sites framework de Django no cumple con lo que necesitamos porque no 
trabaja con bases de datos separadas y otros detalles que no vienen al caso.


Lo que estamos probando entonces es usar un Middleware y un Database router 
que en función del host que está haciendo el request "reconfiguran" el 
proyecto para que sirva el sitio correspondiente, desde su propia base de 
datos, con sus templates, sus media files, etc.

Funciona razonablemente bien y estamos investigando hasta dónde podemos llegar 
con esto de overidear settings en runtime; aunque la documentación de Django 
dice que "eso no se debe".

En el corazón del asunto, está este pedacito de código:
http://nicoechaniz.pastebin.com/QV497dQe

que básicamente lo que hace es guardar valores de settings en el "store" del 
thread que está sirviendo el request y cuando "alguien" trata de usar ese 
setting, responder usando el objeto que se guardó en _thread_locals.

Entonces en nuestro "multisite settings", por ejemplo tenemos:

PAGINATION = DynamicSetting('PAGINATION')

Y después cada site en su settings tiene:
PAGINATION = 5
o lo que corresponda

El Middleware se encarga de mirar qué host hizo el request y setear los 
valores de los DynamicSettings haciendo, por ejemplo
settings.PAGINATION.set(5)

Ese valor se guarda en el _thread_locals y cada vez que alguien llame a 
settings.PAGINATION, el DynamicSetting va a "forwardear" la llamada, que es lo 
que hace este __getattribute__

    def __getattribute__(self, attr):
        if attr == 'setting_name':
            return super(DynamicSetting, self).__getattribute__(attr)
        current = getattr(_thread_locals, self.setting_name, None)
        if hasattr(current, attr):            
            return getattr(current, attr)
        else:
            return super(DynamicSetting, self).__getattribute__(attr)


Todo andaba bonito, hasta que nos cruzamos con un caso en el que alguien 
intenta llamar a float(settings.PAGINATION) y revienta diciendo que float 
necesita un número o un string como parámetro.
Esto se puede reproducir fácilmente[1]

Nuestra idea había sido:
Si cada vez que me preguntan cómo hablo, cómo camino, etc. dejo que responda 
el objeto que tengo en _thread_locals, entonces efectivamente mi objeto se va 
a comportar como ese otro que tengo guardado.

Pero resulta que no. Al menos no con new-style classes y con este método de 
definir __getattribute__; lo que se explica en la documentación de Python 
sobre su datamodel, que dice:
"the special method must be set on the class object itself in order to be 
consistently invoked by the interpreter"[2]

Esto llevó a la sugerencia de Facu de agregar a nuestra clase:
def __float__(self):
    current = getattr(_thread_locals, self.setting_name, None)
    return current.__float

Y obviamente se solucionó ese problema particular pero explotó en la siguiente 
operación que llamaba a un special method.

Terminamos implementando de la misma forma __radd__, __add__, etc. a partir de 
esta lista[3] y salió "andando" y pasamos de tener 15 líneas de código 
relativamente interesante, a un montón de porquería :P


Se escuchan ideas, sugerencias, comentarios sobre cómo mejorar esto y también 
sobre experiencias de deployment de Django con estas mismas limitaciones y 
características. No encontramos en la güeb a nadie que hubiera resuelto esto 
elegantemente.


Salú,

NicoEchániz

PD: al día de hoy, en el repo no van a encontrar esto porque todavía no lo 
comiteamos


[0] http://codigo.cyclope.ws
[1] http://nicoechaniz.pastebin.com/H4gBNytv
[2] http://docs.python.org/reference/datamodel.html#new-style-special-lookup
[3] http://pyref.infogami.com/ -> "special methods, attributes, and variables"

------------ próxima parte ------------
Se ha borrado un adjunto en formato HTML...
URL: <http://listas.python.org.ar/pipermail/pyar/attachments/20101006/71675833/attachment.html>


More information about the pyar mailing list