Mercurial na web com apache e fastcgi
Um efeito interessante do padrão WSGI é que ele pode tornar a vida dos sysadmins mais fácil. Por exemplo, publicar uma aplicação web em Python, com WSGI consiste apenas de:
- Descobrir como instanciar essa aplicação (específico de aplicação pra aplicação)
- Transformá-la em um cliente/middleware WSGI
- Publicá-la, usando a forma desejada.
Abaixo, segue um exemplo de uma publicação do mercurial, no apache, usando fastcgi.
#
# An example CGI script to export multiple hgweb repos, edit as necessary
import site
import sys
site.addpackage('/home/usuario/lib/python2.5/site-packages/','easy-install.pth',set(sys.path))
# enable importing on demand to reduce startup time
from mercurial import demandimport; demandimport.enable()
# If you'd like to serve pages with UTF-8 instead of your default
# locale charset, you can do so by uncommenting the following lines.
# Note that this will cause your .hgrc files to be interpreted in
# UTF-8 and all your repo files to be displayed using UTF-8.
#
#import os
#os.environ["HGENCODING"] = "UTF-8"
from mercurial.hgweb.hgwebdir_mod import hgwebdir
import mercurial.hgweb.wsgicgi as wsgicgi
from flup.server.fcgi import WSGIServer
application = hgwebdir('hgweb.config')
WSGIServer(application, debug = True).run()
E o hgweb.config:
repositorio1 = /home/usuario/hg/repositorio1
repositorio2 = /home/usuario/hg/repositorio2
Compare com o deploy de Pylons via fastcgi, por exemplo. Depois de termos instanciado a aplicação (e isso varia de framework ou aplicativo), a forma de executar como fastcgi no apache é a mesma, usando o flup. E isso serviria para qualquer aplicação que disponibilizasse uma 'saída' WSGI. Prático não?
Deploy simplificado de Pylons via FastCGI
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!
Pylons, WSGI, Frameworks Ruby e Templates Engines
WSGI é uma especificação de comunicação entre servidores web e servidores de aplicação. Pondo de forma grotesca: é como o seu aplicativo em TurboGears ou Pylons se comunica com o Apache, Lighttpd ou qualquer outro servidor. O Cacilhas fez uma implementação de WSGI pra Lua. Na trilha disso, eu dei uma pesquisada e achei o Rack - uma especificação semelhante para o Ruby.
Christian Neukirchen fez um post introdutório sobre o rack onde ele menciona dois frameworks, o Camping e o Ramaze, que já suportam o rack. Como vocês podem ver, o Camping é mais um produto do why the lucky stiff, famoso pelo Poignant Guide to Ruby.
Não cheguei a ver os frameworks - deixo esse trabalho a vocês. Mas vi pelo menos uma coisa interessante:
O Camping usa Ruby pra gerar os templates! Um exemplo, retirado do próprio site do projeto:
def layout
html do
title { 'My HomePage' }
body { self << yield }
end
end
Esquisito? É porque vocês não viram o HAML ainda.
Eu não imaginava que a selva de templates tivesse chegado ao mundo Ruby, mas a página do Ramaze diz que ele suporta 7 templates engines. E não devem ser todas que existem! Será que já existe ZPT pra Ruby? O jeito é continuar com o bom e velho Erb.
Voltando ao Pylons, eu acabei conseguindo me virar com o CGI/FastCGI na segunda-feira. E com ele também não falta opção de escolha: o sistema de templates padrão é o Myghty, mas em breve será trocado para o Mako.
Talvez eu escreva um tutorial sobre Pylons, mas não estou prometendo nada. Mas acho que já testei o suficiente pra dizer que dos frameworks disponíveis pra Python, o que eu achei mais interessante foi ele.
TurboGears no fim de semana
Há duas semanas, eu publiquei um texto chamado 'Django no fim de semana', relatando uma pequena experiência que fiz com o Django.
Na sexta-feira, o Torcato publicou um artigo chamado SQLALchemy no TurboGears. Resolvi tirar o sábado para testar o tutorial.
Usei o PostgreSQL no lugar do MySQL, o que me causou um pouco de dor de cabeça, não por causa do PostgreSQL em si, mas por causa do conector psycopg. Estou usando o Ubuntu Feisty Fawn, que vem com Python 2.5. Eu tinha instalada duas cersões do conector: psycopg e psycopg2. Ao iniciar o servidor do TurboGears com ambas as versões instaladas, ele morria no meio do caminho com core dump. Resolvi remove o psycopg2, e passou a iniciar. Mas não sem problemas: os SQL estavam sendo gerados de forma incorreta (mais especificamente, sem aspas nos campos string). Resolvi fazer o teste com o Python 2.4, reinstalei o psycopg2 e pra minha surpresa funcionou perfeitamente. Pelo visto, coisa está meio bichada na combinação python2.5 e psycopg2. Uma pena.
Se alguém resolver testar, eis o script de criação da tabela, versão PostgreSQL:
id serial,
name varchar(100) NOT NULL DEFAULT '',
PRIMARY KEY(id)
)
CREATE TABLE recipes ( id serial,
category_id int NOT NULL,
title varchar(100) NOT NULL DEFAULT '',
description varchar(255) NULL,
date date NULL,
instructions text NULL,
constraint fk_recipes_categories FOREIGN KEY (category_id) REFERENCES categories(id),
PRIMARY KEY(id)
);
Alguns pontos:
- O TurboGears provê uma ferramenta chamada toolbox, que inclui algumas coisas legais como um interpretador de python para a web (usando ajax), o Model Designer para criar os models e o CatWalk, para o CRUD dos modelos e o WidgetBrowser(mais sobre ele à seguir), entre outros. O Django inclui o admin no seu pacote contrib, porém o admin é voltado ao usuário, enquanto o CatWalk é voltado ao desenvolvedor. Porém, o CatWalk e o ModelDesigner ainda não funcionam com o SQLAlchemy. Um pequeno preço a pagar por sair do padrão

- Gostei do sistema de templates Kid. Os zopistas vão se sentir familiares com ele, e suas construções py:content e py:replace. Você pode querer dar uma olhada no que eles tem em comum.
- Como o tutorial do Torcato foi bem sucinto, resolvi brincar um pouco alêm dele. Baixei o tgcrud no http://www.turbogears.org/cogbin/ . Porém, me senti como se tivesse sido iludido por uma propaganda enganosa: ele não faz a introspecção para saber as colunas do seu objeto. Ele gera o esqueleto do CRUD, porém ele não sai funcionando antes de você dar uma modificada. Na verdade, a propaganda não foi enganosa: ela diz que é dessa forma que funciona. Apenas minha expectativas que eram muitas.
- Achei o código dos controladores parecidos com os do rails. (Eu não cheguei a mexer muito com as views[controllers] do Django, então não posso dizer exatamente como foi essa parte).
- Uma das idéias que eu achei interessante no desenvolvimento Plone+Archetypes são os widgets. Com widgets, você não precisa escrever o HTML para o seu formulário. Você pega o seu modelo, declara que o campo nome é do tipo TextWidget ou algo assim e o template já renderiza. Pode não ser a forma mais flexível de todas, mas é prático.
- No geral, encontrei pouca documentação do TurboGears, e menos ainda sobre a combinação SQL+Alchemy e TurboGears. Para vocês terem idéia, a página que vem em primeiro lugar na busca do google é a própria página do Torcato. Tente você mesmo!
- Eu não conseguiria fazer o deploy da aplicação com o TurboGears no meu site, devido à versão do meu apache e mod_python. Poderia tentar usando FastCGI e ver se eu não fico frustrado como esse cara.
Resumindo: foi um pouco mais difícil mexer com o TurboGears que com o Django, mas note-se que eu fui muito mais 'raso' no Django - não cheguei a escrever praticamente nenhum template, e os aplicativos em si eram diferentes. Achei pouca documentação do TurboGears. Dei uma olhada por alto no SQLAlchemy e fui com a cara do mesmo - dá pra usar DataMapper ou ActiveRecord com ele. Eu diria que no geral o TurboGears ficou com um pontinho a menos que o Django.
