[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