Deploy simplificado de Pylons via FastCGI

por Walter Cruz on 30/03/2008
in Python, Pylons

Em um pequeno projeto que fiz com Pylons, o arquivo a ser executado pelo FastCGI era como o seguinte:


#!/usr/bin/env python2.5
import os, sys
lib_path = "/home/usuario/lib/python2.5/site-packages/"
sys.path.append(lib_path)
sys.path.append('/home/usuario/projects_pylons/project/')
from pkg_resources import require
require("Pylons")
require('python-openid')
require("AuthKit")
require("Elixir")
require("flup")
require('docutils')
require("pygments")
from paste.deploy import loadapp
wsgi_app = loadapp('config:/home/usuario/projects_pylons/project/server.ini')
from flup.server.fcgi import WSGIServer
WSGIServer(wsgi_app, debug = True).run()
 

Mas qual a razão de todos esses requires? A questão é que todos essas bibliotecas são eggs instaladas no diretório $HOME do usuario. Numa sessão normal do interpretador python, ao ser iniciado, ele detecta todos os eggs disponíveis em PYTHONPATH e os adiciona ao sys.path. Assim, ao fazer o import de algo que está em um egg, ele consegue encontrar a versão correta. Você pode até mesmo fazer um pequeno teste: execute o interpretador python, importe o módulo sys e imprima o sys.path. Você vai ver uma lista de todos os paths disponíveis, inclusive os seus eggs lá.

Mas executando num ambiente fastcgi, a variável PYTHONPATH não estava definida, e essa pequena magia de adicionar os módulos em eggs no sys.path acontece na hora da inicialização do interpretador. Por isso o require: depois que eu adicionei o /home/usuario/lib/python2.5/site-packages/ ao sys.path, cada chamada ao pkg_resources.require irá procurar o egg correspondente, inclusive nesse path. Mas observe que são sete linhas apenas com require (e isso porque o require do Elixir já trouxe o SQLAlchemy de brinde, se não teria sido mais uma. Provavelmente uma boa saída teria sido fazer uma instalação completa do python no meu $HOME, ou usar algo como o virtualenv, mas não era da forma como eu queria.

Minha primeira idéia foi usar o módulo glob, procurar por todos os eggs e adicionar no meu sys.path Isso funcionaria, exceto por um inconveniente: ao instalar uma nova versão do pacote, o easy_install não apaga o egg anterior. E se houvessem mais de uma versão do pacote nos eggs, qual ele usuaria, a primeira versão encontrada na lista? Era hammeragem o bastante. Após alguma pesquisa, consegui enxugar o código para:


#!/usr/bin/env python2.5
import sys
sys.path.append('home/usuario/projects_pylons/project')
import site
site.addpackage('/home/usuario/lib/python2.5/site-packages/','easy-install.pth',set(sys.path))
from paste.deploy import loadapp
wsgi_app = loadapp('config:/home/usuario/projects_pylons/project/server.ini')
from flup.server.fcgi import WSGIServer
WSGIServer(wsgi_app, debug = True).run()
 

O segredo todo estava no módulo site. Com isso, todos os eggs disponíveis em home/usuario/projects_pylons/project estão disponíveis para importação!