Frix – Framework em PHP

Coloquei no meu Github o Frix, um framework em PHP baseado no Django (em Python).

Sempre vi um pouco de dificuldade em vender projetos com Django, os clientes costumam ter uma hospedagem que não suporta Python e oferecem resistência em trocar, ou têm receio de investir em uma solução que usa uma linguagem “exótica”. Pensando nisso, há algum tempo atrás desenvolvi o Frix, tentando aplicar algumas boas idéias do Django no PHP.

O projeto foi pouco utilizado e ficou encostado por muito tempo, mas como acredito que existem coisas que podem ser aproveitadas, resolvi liberar o código antes que caísse no meu próprio esquecimento.

Algumas das principais features:

  • Admin (scaffolding): criar, listar, editar e remover objetos:
    • suporte a hierarquia (árvores);
    • ordenação manual;
    • edição de objetos relacionados (inline);
  • Mapeador objeto-relacional: modelos de dados, abstração de consultas:
    • com suporte a MySQL;
  • Roteador de URLs com expressões regulares;
  • Sistema de templates em PHP puro:
    • suporte a herança e blocos;
    • aviso: a performance deixa a desejar;
  • Tratamento de Erros;

Para testar:

  1. clonar o projeto:
  2. git clone git://github.com/ricobl/frix.git
  3. criar o arquivo index.php:
  4. <?
    // Replace the path according to your setup
    require_once('path/to/frix/main.php');
    Frix::start();
    ?>
  5. criar o arquivo config.php:
  6. <?
    $config['DB_URL'] = 'mysql://user:pass@localhost/database';
    ?>
  7. criar o banco de dados no mysql, usando os SQLs das apps para criar as tabelas necessárias.

De qualquer forma, não pretendo manter o projeto, faz algum tempo que não trabalho com PHP e já não o acho mais tão divertido. Um dos motivos que me fez deixá-lo de lado foi a fraca orientação à objetos e dificuldade de instrospecção do PHP.

Gostaria de ouvir as opiniões sobre o framework e mais ainda se alguém achar algum uso para ele ou algum de seus módulos.

Novo portal Rádio Banda B

UPDATE 12/03/2010: eu não trabalho com a equipe da rádio, apenas trabalhei no desenvolvimento do portal. Para falar com a equipe, acesse o portal.

UPDATE 14/07/2010: É engraçado que mesmo depois de colocar o aviso acima continuo recebendo pedidos dos mais variados em relação à rádio. Pessoas elogiando os programas, procurando casa para alugar, comprar, entre outros.

UPDATE 30/08/2010: Desisti! Comentários fechados.

Está no ar mais um portal em que eu tive o prazer de trabalhar aqui na Orangotoe: Rádio Banda B. A rádio é líder no seguimento AM em Curitiba e Região Metropolitana, seu conteúdo é voltado para a informação e entretenimento da comunidade, contendo trechos de programas da rádionotíciasblogs, agenda de programação e vários outros recursos.

O mais interessante deste projeto foi o modo em que foi desenvolvido, usando uma mistura de Scrum com XP. Foi aplicada uma série de boas práticas como desenvolvimento orientado a testes, programação em par e aplicações reutilizáveis. Vários módulos e aplicações de código aberto foram utilizados e outros criados internamente que poderão ser reaproveitados em novos projetos. Tudo isso contribuiu para que o processo fosse mais fluido e rápido.

Um destaque para a aplicação de calendário que suportou o agendamento e recorrência da programação da rádio. Um agradecimento especial ao Gustavo Niemeyer pelo módulo python-dateutil com as classes rrule e rruleset que foram fundamentais para o funcionamento desta aplicação.

Para saber mais basta visitar o portal! Estarei aqui para responder qualquer pergunta ou comentário, qualquer opinião é bem vinda!

Para ter um registro histórico, aqui vai um screenshot da home:

Screenshot da home do portal Rádio Banda B AM 550

ParseException com django-haystack e Whoosh

O haystack, utilitário de buscas para o Django, tem um problema quando se tenta listar todas as ocorrências indexadas para um único model.

Estou usando o Whoosh como backend de busca, não sei se isso acontece com outros backends.

Eu alertei o desenvolvedor, mas ainda não houve tempo de resposta pelo jeito já foi corrigido.

É possível contornar o problema filtrando pelo campo de content-type do índice ao invés de usar o método sugerido “models”. Algo assim:

SearchQuerySet().filter(django_ct='%s.%s' % (SomeModel._meta.app_label,
                                             SomeModel._meta.module_name))

Pesquisa sobre a Comunidade PythonBrasil

O Henrique Bastos vai estar na PyCon deste ano, em Atlanta nos Estados Unidos, e vai apresentar uma palestra “Small acts make great revolutions”.

Para isso está pedindo colaboração para traçar um perfil da comunidade Python no Brasil através de uma pesquisa. Se você é desenvolvedor, faça sua parte e preencha a pesquisa, é rápido e indolor. Não precisa trabalhar com Python, o perfil é geral.

Pesquisa:
http://henriquebastos.wufoo.com/forms/vamos-divulgar-nossa-comunidade-python-na-pycon/

Post inicial:
http://henriquebastos.net/2010/01/08/ajude-a-mostrar-a-pythonbrasil-na-pycon-2010/

Bombando:
http://henriquebastos.net/2010/01/13/a-pesquisa-sobre-a-comunidade-pythonbrasil-esta-bombando/

Como não vi nada no feed da Django Brasil, tomei a liberdade de “taguear” o post para aparecer lá também, sei que tem gente que acompanha novidades do Django mas não do Python em geral.

django-importer: Software Livre Compensa

Há um tempo atrás, desenvolvi uma aplicação para Django que achei bacana e resolvi lançar como um projeto no Google Code.

Divulguei nos canais competentes mas não houve muito alarde. Algumas pessoas pediram mais explicações mas parou por aí, o projeto ficou encostado.

Passado um tempo, o Josir me perguntou se eu poderia criar um módulo para importação de arquivos CSV. Combinamos um preço, o projeto continuaria aberto e eu desenvolvi o novo recurso.

Fiz melhorias além do requisitado, no código, na documentação e inclui um projeto de exemplo. O resultado já foi publicado e está disponível para quem quiser ver e usar.

O melhor dessa história toda é o reconhecimento, me senti muito bem em ver que o trabalho foi útil para alguém e que existe gente disposta a investir para torná-lo melhor.

Combinei com o Josir que iria divulgar o ocorrido, pode parecer algo pequeno, mas para mim foi significante e acho que serve como um bom exemplo de colaboração. Por falta de tempo, ele ainda não pode testar, mas teve o que precisava e com isso a comunidade também ganhou.

Então deixo aqui os meus agradecimentos ao Josir e à comunidade de software livre.

Auto-completar com o Fabric

Estive fazendo algumas coisas com fabric e senti falta de um auto-completar para as tarefas disponíveis.

Aí descobri que era possível listar as tarefas usando:

fab --list

Dei uma olhada no autocomplete do Django e também encontrei este artigo que me ajudou bastante.

Juntando um pouco daqui e dali cheguei neste script que deixei junto das minhas dotfiles.

Parece coisa de preguiçoso, normalmente as tarefas têm pouco mais de 4 letras, mas tudo bem, ajudar não dói não é?

Para instalar é só chamar o script no seu .bashrc ou .bashprofile:

. /caminho/para/fab_bash_completion

Também serve de exemplo para fazer o auto-completar dos seus próprios comandos.

Apache, processos, threads e django

Muitas vezes apenas adotamos os padrões e não nos questionamos porque eles foram escolhidos, então resolvi fazer uma breve pesquisa sobre threads e processos no Apache

Antes de mais nada, é importante falar um pouco sobre processos e threads. Os processos são independentes e têm seus próprios endereços de memória, os threads pertencem a um processo e compartilham o mesmo endereço de memória e recursos.

Tudo começa no webserver, vou falar do Apache. Existem dois sabores que se diferenciam em como será feito o processamento de múltiplas requisições: prefork e worker.

O prefork tem um processo de controle para outros sub-processos que irão receber e responder às requisições. Aqui não são utilizados threads, uma requisição é repassada para o primeiro sub-processo livre e este, por sua vez, só atenderá a uma requisição por vez.

O worker também tem um processo de controle para outros sub-processos, a diferença é que cada sub-processo cria um thread de escuta que fará o repasse de requisições para outros threads. Isto faz com que o uso de memória normalmente seja menor, pois os threads compartilham os mesmos recursos.

Em ambos os modelos é possível regular o mínimo ou máximo de processos e threads, o Apache se encarrega de reciclar os processos dentro destes limites. O ideal é que o servidor esteja configurado para aguentar o número esperado de requisições mas evitando consumir toda a RAM disponível, o que resultaria em uso da SWAP em disco e uma cascata de problemas.

Segundo a documentação, o prefork é indicado para evitar threading em aplicações que utilizam bibliotecas non-thread-safe, ou seja, evitar que ações em threads diferentes de um mesmo processo possam influenciar outro thread. Como o worker mantém um conjunto de threads em um mesmo processo, compartilhando recursos, bibliotecas mal preparadas podem causar resultados inesperados.

Em geral, o Django é tido como thread-safe, houve uma discussão sobre isso mas acredito que muitos dos problemas da época já tenham sido resolvidos. Ainda não fiz a experiência, mas este post do Armin Ronacher compara o sistema de templates do Django com o Jinja e comenta que algumas templatetags como a cycle não são thread-safe.

Por muito tempo o Django teve o modpython com Apache prefork como deploy recomendado, agora a recomendação oficial é para se utilizar o modwsgi* mas não cobre qual modelo de webserver é mais adequado.

* Curiosidade: conversando com o Jacob na Python Brasil aprendi que se pronuncia “mód uísgui”, mais fácil não? 🙂

Aqui vão mais alguns links interessantes:

Manager para diferentes conexões de banco no Django

Pra tirar um pouco as teias de aranha do blog, vou escrever sobre algo que fiz recentemente e achei bacana.

Eu estava trabalhando em um novo projeto (em Django, é claro), vinculado a um projeto principal e comecei a me sentir um pouco amarrado. Os dois projetos compartilhavam as mesmas aplicações mas depois de algumas definições no meio do caminho pode-se ver que eles precisariam compartilhar pouquíssimo conteúdo.

Há algum tempo, havia visto um manager que era capaz de se conectar a um banco diferente do padrão e achei que esta seria a solução ideal. Eu poderia separar o conteúdo dos dois projetos e, quando precisasse, usaria o banco de dados do outro projeto.

O manager que eu havia visto estava em algum destes projetos em Django que foram abertos ao público recentemente, mas não consegui mais encontrar. Então me vi obrigado a fazer um pouco de pesquisa e inventar o meu próprio.

Acabei encontrando uma solução do Eric Florenzano e outra do Kenneth Falck.

A do Erick parecia um tanto antiga e junto com a pesquisa esbarrei na changeset 10026, em que foi alterada a maneira de se conectar ao banco, ao invés de usar o módulo django.conf.settings o parâmetro passa a ser um dicionário com dados de conexão, e isso facilitou bastante.

Chega de papo furado e vamos ao código. Vou explicando por partes.

Primeiro foi preciso definir uma maneira fácil de usar conexões diferentes sem ficar repetindo no código as configurações de cada conexão. A solução copiada do Eric foi criar um dicionário de conexões nas configurações do projeto:

# Configurações padrão do banco
DATABASE_ENGINE = 'postgresql_psycopg2'
DATABASE_NAME = 'default_db_name'
DATABASE_USER = 'user'
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''

# Configuração padrão + alternativas
DATABASES = {
    'default': {},
    'alternative': {
        'DATABASE_NAME': 'alternative_db_name',
    },
}

Como eu disse, quiz deixar fácil o uso de conexões diferentes, então fiz com que não fosse necessário repetir os dados de configuração de cada conexão, visto que é comum que se use o mesmo usuário, senha, engine, etc. Então se você pretende somente usar outro banco, basta mudar o DATABASE_NAME. Abaixo o código responsável por isto:

from django.conf import settings
from django.db import models, backend

# Dicionário de conexão padrão
db_settings = {
    'DATABASE_HOST': settings.DATABASE_HOST,
    'DATABASE_NAME': settings.DATABASE_NAME,
    'DATABASE_OPTIONS': settings.DATABASE_OPTIONS,
    'DATABASE_PASSWORD': settings.DATABASE_PASSWORD,
    'DATABASE_PORT': settings.DATABASE_PORT,
    'DATABASE_USER': settings.DATABASE_USER,
    'TIME_ZONE': settings.TIME_ZONE,
}

def prepare_db_settings(db_profile_name):
    """
    Recebe um dicionário de conexão alternativa e retorna um novo dicionário,
    completando propriedades ausentes com os valores padrão.
    """
    return dict(db_settings, **settings.DATABASES[db_profile_name])

Por fim, o manager se comporta normalmente, se você quiser conectar a um banco diferente é só usar o método use, informando o nome do perfil de conexão que deseja utilizar. O manager retorna a query padrão mas aponta para outro banco de dados. Bacana não?

class MultiDBManager(models.Manager):
    """
    A manager that can connect to different databases.
    """
    def use(self, db_profile_name):
    	"""
    	Return a queryset connected to a custom database.
    	"""
        # Get customized database settings to use in a new connection wrapper
        db_settings = prepare_db_settings(db_profile_name)

    	# Get the queryset and replace its connection
        qs = self.get_query_set()
        qs.query.connection = backend.DatabaseWrapper(db_settings)
        return qs

Mas nem tudo são flores, é tudo muito experimental e não foi testado a fundo. Aqui vão alguns problemas conhecidos:

  1. Você vai encontrar erros se tentar acessar models relacionados, o manager inicial vai usar a conexão alternativa, mas o model relacionado vai ser procurado no banco padrão. Para contornar isto use o método select_related(‘model_relacionado’), desta forma todos os models serão listados em apenas uma consulta ao mesmo banco.
  2. Não foi testado nenhum tipo de alteração de dados (inclusão, edição, exclusão). O post do Eric talvez possa ajudar a encontrar uma solução neste sentido.

De qualquer forma, tem sido bastante útil para integrar projetos sem fazer muita bagunça.

E você? O que achou? Deixe sua opinião nos comentários!

* Disponibilizei o código no Django Snippets também: Manager for multiple database connections.

Django UnicodeEncodeError com upload usando WSGI

Se você se deparou com um UnicodeEncodeError ao fazer o upload de algum arquivo com caracteres especiais usando WSGI no Django, a solução pode estar aqui: How to use django with mod_wsgi – Additional Tweaking.

Eu passei pelo seguinte erro:

UnicodeEncodeError: 'ascii' codec can't encode characters
in position 86-87: ordinal not in range(128)

Em resumo é preciso alterar as variáveis de ambiente LANG e LC_ALL do Apache:

export LANG='en_US.UTF-8'
export LC_ALL='en_US.UTF-8'

Isto é feito alterando o arquivo envvars do Apache, normalmente em /etc/apache2/envvars.

Em algumas instalações / distribuições, este arquivo pode não existir ou não estar sendo chamado no httpd.conf, neste caso é preciso configurar manualmente.

No FreeBSD é preciso criar um arquivo com a extensão .env em /usr/local/etc/apache2/envvars.d. Como isto é ligado ao WSGI, chamei de mod_wsgi.env.

Depois de alterar é preciso reiniciar o apache.

Agora uploads de arquivos nomeados com caracteres especiais funcionam normalmente.

Lançamento do django-importer

Read this post in English / Leia este post em Inglês

Eu tenho criado alguns importadores para models do Django e me vi repetindo um padrão, mesmo com origens de dados bem diferentes, pude encontrar um certo nível de abstração.

O resultado foi um novo projeto chamado django-importer, com a intenção de servir como base para qualquer importação no Django.

Está distribuído sob a licença BSD e hospedado no Google Code: http://code.google.com/p/django-importer/.

Estou escrevendo um projeto de exemplo para ajudar no uso. É um projeto novo, mas bastante funcional (pelo menos para mim), e há bastante a ser melhorado.

Colaboradores, comentários e sugestões são bem-vindos, é só entrar em contato aqui pelo blog ou pela página do projeto.

Crie um site como este com o WordPress.com
Comece agora