Sugestão de livro: Two Scoops of Django

2scoops

O Django é uma baita ferramenta que auxilia muitos desenvolvedores a concretizar seus projetos web com agilidade e simplicidade impressionantes. A documentação do framework é bastante vasta. São blogs de desenvolvedores, listas de email, livros bem completos, a trilha no StackOverflow, além de muitos e muitos projetos abertos no GitHub e BitBucket, e é claro, a excelente e completíssima documentação oficial. Até aí, tudo perfeito. Material para iniciantes querendo aprender Django existe de monte, mas quando as dúvidas começam a ficar um pouco mais específicas, ou questões relacionadas à boas práticas em projetos Django, a coisa começa a ficar mais escassa. Felizmente para nós, Djangonautas, o Daniel Greenfeld e a Audrey Roy começaram a resolver um pouco desse problema escrevendo o excelente Two Scoops of Django: Best Practices for Django 1.5.

O livro não é um tutorial e tampouco uma documentação exaustiva do Django, mas sim uma valiosa coleção de dicas e conselhos sobre boas práticas em projetos Django, atualizada para a versão 1.5. Já nos primeiros capítulos, fiquei com aquela sensação de “putz, eu tô fazendo tudo do jeito mais difícil nos meus projetos!”. Os autores vão mostrando os problemas e apresentando as soluções de uma forma bem prática, passando dicas, alertas, e, o que achei mais legal de tudo, as Package Tips, que são dicas sobre pacotes de terceiros que os autores costumam usar em seus projetos e que são uma verdadeira mão-na-roda.

Talvez você esteja pensando consigo próprio: “ah, eu já vi várias coisas dessas espalhadas pela web…”. Aí é que está o ponto principal, pois os autores pegaram a vasta experiência que possuem e compilaram uma série de dicas em um só lugar. E quando falo de dicas, não pense que são trechinhos pequenos de texto com links para outros recursos. Pelo contrário, os autores se preocuparam em explicar bem o porquê das coisas, sem cansar o leitor.

Outra coisa que achei interessante é que, diferentemente de um monte de livros que a gente vê por aí, parece que os autores deixaram de lado a preocupação de que o livro deles possa ficar obsoleto por passar dicas pontuais de pacotes específicos para resolver determinados problemas. Me parece que muitos autores limitam a abrangência de seus livros por medo de abordar um assunto mais específico, que poderia sofrer mudanças em breve (talvez o sentimento de estar sendo eternizado pelo livro deixe alguns autores meio confusos). Os autores do Two Scoops of Django não se preocuparam muito com isso e até se comprometeram em publicar erratas caso alguns elementos sofram mudanças nos próximos tempos.

O livro em si é muito bem organizado, com um formato muito bom para a leitura. Os autores se preocuparam MUITO e conseguiram fazer um layout excelente para ser lido em e-readers. Eu comprei a versão para Kindle, e esse é o primeiro livro técnico que leio em que não é preciso ficar diminuindo o tamanho da fonte para conseguir ler decentemente os trechos de código. Parabéns aos autores pela preocupação com os leitores da versão digital do livro!

O conteúdo

Não vou fazer aqui uma análise completa do livro. Vou listar apenas algumas coisas importantes que aprendi com o livro:

  • Como estruturar meus projetos Django;
  • Que as class-based-views são muito fáceis de usar;
  • Que na versão 1.5 do Django ficou barbada estender o modelo User;
  • Que realizar processamento nos templates é roubada;
  • Que dá pra manter configurações (settings.py) específicas para diferentes ambientes;
  • Que import relativo existe; (isso mesmo, eu não conhecia esse recurso)
  • Que select_related() quebra um galhão pra consultas grandes;
  • E muitas outras coisas! (muitas mesmo!) 🙂

Enfim, o conteúdo do livro é fantástico! Recomendo a todo mundo que tem um pouquinho de experiência com o Django que compre e leia esse livro. Não é preciso ser especialista no framework para se aproveitar do conteúdo dele. Se você está na dúvida se o livro é adequado para você, dê uma conferida no conteúdo dele na página oficial.

Eu recomendo!

De 0 a 10, dou nota 10 para esse livro. Li ele apenas uma vez, mas já vou começar a reler para fixar bem as dicas, pois são muitas coisas novas.

Se quiser seguir minha dica, estão à venda as versões impressa e digital do livro. Comprando direto pela página do livro, é possível comprar o pacote digital (formatos PDF, mobi e ePub, tudo DRM-free) por 17 dólares (preço em 22/06/2013). Na Amazon americana, está à venda a versão impressa. E ainda, se quiser comprar pela Amazon Brasil, eles estão vendendo a versão para Kindle.

Se ainda estiver na dúvida se o livro vale mesmo a pena, leia os reviews dos leitores na Amazon.

Conjuntos em Python

Hoje percebi que o blog já tratou sobre listas, dicionários, tuplas; mas até agora não escrevi texto algum sobre os conjuntos (também conhecidos por sets).

Sets: o que são?

Sets (ou, como iremos chamar daqui para a frente, conjuntos) são estruturas disponíveis como builtins do Python, utilizadas para representar coleções desordenadas de elementos únicos. É importante sempre lembrar dos conjuntos por suas duas principais características:

  1. Os elementos não são armazenados em uma ordem específica e confiável;
  2. Conjuntos não contém elementos repetidos.

A característica número 1 é importante, porque o desenvolvedor jamais deve confiar na ordenação de um conjunto, visto que a ordem em que os elementos são mantidos nos conjuntos varia de implementação para implementação do interpretador Python. Não é a toa que conjuntos não suportam indexação nem fatiamento (slicing). Bom, chega de papo e vamos ver alguns exemplos de conjuntos.

Mãos na massa

Vamos começar definindo um conjunto, usando a sintaxe para literais de conjuntos (introduzida há pouco tempo nas versões 3.1 e 2.7):

>>> s = {1, 2, 3, 4}
>>> print s
set([1, 2, 3, 4])

Existem várias operações disponíveis nos conjuntos através de métodos, como as operações mais conhecidas de teoria dos conjuntos, como união, interseção e diferença.

União

Imagem da Wikipedia

A U B (Crédito da imagem: Wikipedia)

>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> print a.union(b)
set([1, 2, 3, 4, 5, 6])

Interseção

Imagem da Wikipedia

A ∩ B (Crédito da imagem: Wikipedia)

>>> print a.intersection(b)
set([3, 4])

Essa operação é muito útil quando precisamos descobrir elementos que duas listas possuem em comum:

>>> l1 = [1, 2, 3]
>>> l2 = [2, 4, 3]
>>> print set(l1).intersection(l2)
set([2, 3])

Perceba que convertemos l1 para conjunto para podermos usar o método intersection; já l2 não precisou ser convertida, pois esses métodos exigem que apenas o primeiro argumento seja um conjunto. Poderíamos obter o resultado da interseção como uma lista também:

>>> l3 = list(set(l1).intersection(l2))
>>> print l3
[2, 3]

O método intersection não modifica os conjuntos recebidos como parâmetro. Se quisermos que o resultado da interseção seja gravado como novo valor do primeiro conjunto, ao invés de retornar o novo conjunto como resultado, podemos usar o método intersection_update:

>>> a.intersection_update(b)
>>> print a
set([2, 3])

Diferença

Imagem da Wikipedia

B \ A (Crédito da imagem: Wikipedia)

A diferença entre dois conjuntos A e B retorna somente os elementos de A que não estão em B, ou seja, retira de A todos os elementos comuns a ambos os conjuntos:

>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> print a.difference(b)
set([1, 2])
>>> print b.difference(a)
set([5, 6])

Observe que a.difference(b) é o mesmo que a \ b e que b.difference(a) é o mesmo que b \ a.

Assim como o método anterior, difference também não altera o conjunto sobre o qual é chamado. Para alterá-lo, é necessário usar o método difference_update().

Diferença simétrica

Diferença simétrica é uma operação sobre os dois conjuntos, que retorna todos os elementos (de ambos os conjuntos a e b) que pertencem a somente um dos conjuntos.

>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> print a.symmetric_difference(b)
set([1, 2, 5, 6])

Diferença Simétrica

A △ B (Crédito da imagem: Wikipedia)

Pertinência

Além das operações tradicionais de união, interseção e diferença, também temos operações de verificação de pertinência. A seguir veremos algumas.

Para verificar se um determinado elemento pertence a um conjunto, podemos usar o já conhecido operador de pertinência in:

>>> a = {1, 2, 3, 4}
>>> b = {3, 4, 5, 6}
>>> 1 in a
True
>>> 5 in a
False

Também podemos verificar se um conjunto é um subconjunto de outro:

>>> a = {1, 2, 3, 4}
>>> c = {1, 2}
>>> c.issubset(a)
True
>>> a.issubset(c)
False

Além disso, podemos verificar se um conjunto é superconjunto de outro:

>>> a.issuperset(c)
True

Outra relação importante que podemos checar é a disjunção entre dois conjuntos. Dois conjuntos são disjuntos se tiverem interseção nula. Exemplo:

>>> c = {1, 2}
>>> d = {3, 4}
>>> c.isdisjoint(d)
True

Removendo elementos duplicados de uma sequência

Por definição, um conjunto é uma coleção de valores únicos (e desordenados). Assim sendo, se passarmos ao construtor de conjuntos uma lista com valores repetidos, esses valores serão eliminados de forma a permanecer apenas um deles. Exemplo:

>>> lista = [1, 1, 2, 3, 5, 8]
>>> conjunto = set(lista)
>>> print conjunto
set([8, 1, 2, 3, 5])

Ou, se quisermos ter de volta uma lista:

>>> lista = list(set(lista))

ATENÇÃO: a operação acima pode (e, com grandes chances, irá) bagunçar a lista. Ou seja, a ordem original irá se perder.

>>> print lista
[8, 1, 2, 3, 5]

Leia mais

Entendendo os decorators

Hoje me deparei com um excelente texto sobre decorators que me inspirou a escrever algo sobre o assunto que para muita gente ainda é um tanto quanto nebuloso. Vou tentar aqui explicar o funcionamento de um decorator e mostrar algumas possíveis aplicações.

Aviso aos iniciantes: esse assunto pode ser um pouco confuso para quem ainda está iniciando em programação. Caso sinta dificuldades, não desanime e pule antes para a seção que contém as referências para melhor entendimento do texto.

O que é um decorator?

Um decorator é uma forma prática e reusável de adicionarmos funcionalidades às nossas funções/métodos/classes, sem precisarmos alterar o código delas.

O framework para desenvolvimento web Django oferece diversos decorators prontos para os desenvolvedores. Por exemplo, para exigir que o acesso a determinada view seja feito somente por usuários autenticados, basta preceder o código da view (que em geral é uma funçãozinha ou classe) pelo decorator @login_required. Exemplo:

@login_required
def boas_vindas(request):
    return HttpResponse("Seja bem-vindo!")

É claro que isso não é mágica. Como a gente pode ver no código-fonte do decorator login_required, os detalhes estão apenas sendo ocultados do código-fonte do nosso projeto. Assim, ao invés de ter que, a cada view, escrever o código que verifica se determinado usuário está autenticado, basta usar o decorator. Isso faz com que adicionemos a funcionalidade de verificar se um usuário está ou não logado no site, com uma linha de código apenas. Que barbada, não?

O decorator é um açúcar sintático que Python oferece aos desenvolvedores desde a versão 2.4, facilitando o desenvolvimento de códigos reusáveis.

OK, mas como implementar um decorator?

Você já sabe como um decorator pode ser usado, então agora vamos entender as internas desse recurso do Python.

Um decorator é implementado como uma função que recebe uma função como parâmetro, faz algo, então executa a função-parâmetro e retorna o resultado desta. O algo é a funcionalidade que adicionamos a nossa função original através do decorator.

Vamos escrever um decorator que sirva para escrever na tela o nome da função a ser executada, antes da execução da mesma. Como descrito acima, precisamos definir uma função que receba outra função como parâmetro, imprima o nome dessa, execute a função e retorne o seu resultado. Veja o código:

def echo_funcname(func):

    def finterna(*args, **kwargs):
        print "Chamando funcao: %s()"  % (func.__name__)
        return func(*args, **kwargs)

    return finterna

@echo_funcname
def dobro(x):
    return x*2

dobro(10)

Antes de mais nada, observe atentamente a função echo_funcname, pois existem alguns conceitos importantes dentro dela.

def echo_funcname(func):

    def finterna(*args, **kwargs):
        print "Chamando funcao: %s()"  % (func.__name__)
        return func(*args, **kwargs)

    return finterna

Veja que ela receba um parâmetro func (que espera-se que seja uma função) e retorna outra função (finterna). A função retornada, finterna, é “configurada” para executar ao seu final a função recebida como argumento pela função externa (echo_funcname), bem como retornar o valor de retorno da função recebida. Em outras palavras, echo_funcname() cria dentro de si próprio uma função chamada finterna(), que no final (linha 5) chama a função recebida como parâmetro. Mas, é importante perceber que a palavra-chave def somente cria a função (isto é, instancia um objeto do tipo função), não executando ela. Ou seja, echo_funcname cria uma função, configura ela para executar func() ao seu final, não a executa, mas sim somente retorna o objeto função, que então poderá ser chamada por quem recebê-la. (um assunto muito importante para o entendimento desse conceito de função dentro de função é o conceito de closures).

Caso tenha ficado confuso, perceba que finterna é um objeto como qualquer outro que estamos acostumados a criar dentro de nossas funções, como uma lista, por exemplo. A diferença é que esse objeto é uma função, o que pode parecer um pouco estranho, em um primeiro momento. Sendo um objeto qualquer, a função é instanciada, recebe um nome (finterna), e pode ser retornada, assim como todo objeto (tudo isso sem ser executada, pois não chamamos finterna).

Veja um exemplo de visualização de uma função que define outra função internamente (visualização gerada pelo excepcional pythontutor.com):

func

Se quiser visualizar a versão interativa, clique aqui (powered by PythonTutor.com).

Tendo a função echo_funcname() definida, agora poderíamos fazer o seguinte:

def echo_funcname(func):

    def finterna(*args, **kwargs):
        print "Chamando funcao: %s()"  % (func.__name__)
        return func(*args, **kwargs)

    return finterna

def dobro(x):
    """ Uma funcao exemplo qualquer.
    """
    return 2*x

dobro_com_print = echo_funcname(dobro)
print dobro_com_print(10)

Ao executar o código acima, teremos como resposta na tela:

Chamando funcao: dobro()
20

Criamos uma função chamada dobro(), que recebe um número e retorna o dobro desse número. Depois, passamos esse objeto do tipo function para a função echo_funcname() e recebemos como retorno outro objeto do tipo function, ao qual referenciamos como dobro_com_print. Perceba que dobro_com_print nada mais é do que uma referência a uma função mais ou menos assim:

def finterna(*args, **kwargs):
    print "Chamando funcao: %s()"  % (dobro.__name__)
    return dobro(*args, **kwargs)

Essa função foi gerada dentro de echo_funcname() e retornada, já com dobro no lugar de func. Assim, quando chamamos a função como em print dobro_com_print(10), estamos chamando a função acima, e passando 10 como argumento.

Mas, esse negócio todo de passar uma função como parâmetro e receber uma função como retorno de uma chamada de função é um pouco confuso. Para abstrair um pouco esses detalhes, Python oferece a sintaxe do @nome_do_decorator que precede a definição de funções. Assim, ao invés de:

dobro_com_print = echo_funcname(dobro)
print dobro_com_print(10)

Poderíamos apenas preceder a definição da função dobro() com o decorator @echo_funcname:

@echo_funcname
def dobro(x):
    """ Uma funcao exemplo qualquer.
    """
    return 2*x

Agora, ao chamar a função dobro(), estaríamos chamando a função decorada (isto é, acrescida de funcionalidades). No nosso caso, o decorator apenas adiciona a impressão na tela de um aviso sobre a chamada da função.

Enfim, um decorator nada mais é do que uma função que recebe outra função como parâmetro, gera uma nova função que adiciona algumas funcionalidades à função original e a retorna essa nova função.

Concluindo …

Os decorators formam um recurso muito importante para diminuir a duplicação e aumentar o reuso de código em um projeto. O conceito pode ser um pouquinho complicado para entender de primeira, mas uma vez que você o domine, você começará a perceber diversas oportunidades para implementar e usar decorators em seus projetos.

Leia mais

Por se tratar de um assunto mais complicado para iniciantes, segue aqui uma lista de textos que poderiam ser lidos, possibilitando um melhor entendimento sobre o assunto.

Funções como objetos:

Closures:

Retirando elementos vazios de uma lista

Há poucos dias, lendo um código escrito pelo amigo Elias Dorneles, me deparei com uma forma bem elegante de desconsiderar os elementos vazios ou nulos de uma lista:

lista = ['algumas', 'palavras', '', 'e', 'nada', 'mais', '']
lista = filter(None, lista)
# lista fica: ['algumas', 'palavras', 'e', 'nada', 'mais']

Legal, não? Pois é, isso está na documentação da função:

filter(function, iterable)
Construct a list from those elements of iterable for which function returns true. iterable may be either a sequence, a container which supports iteration, or an iterator. If iterable is a string or a tuple, the result also has that type; otherwise it is always a list. If function is None, the identity function is assumed, that is, all elements of iterable that are false are removed.

Note that filter(function, iterable) is equivalent to [item for item in iterable if function(item)] if function is not None and [item for item in iterable if item] if function is None.

. . .

Ou seja, se passarmos None como valor para o parâmetro function, filter irá retornar uma nova lista contendo somente os elementos da lista original que não forem avaliados como False.

Templates de páginas com Python e Google App Engine

Sumário

def get(self):
    self.response.out.write("<html><head><title>Lista de Comentários</title></head><body>")
    for c in Comentario.objects.all():
        self.response.out.write("""<p>Nome: %s</p><p>Email: %s</p>
            <p>URL: %s</p><p>Comentário: %s</p>""" % 
                (c.nome_usuario, c.email, c.url, c.comentario))
    self.response.out.write("</body></html>")

Diga aí, o que você achou da implementação do método get() no código acima? Esse monte de HTML embutido em strings no código Python fica horrível, não?

Pois então, este post vai mostrar uma forma mais adequada para usar conteúdo em HTML no seu projeto web: os templates (ou, em bom português, modelos).

Templates são HTMLs açucarados

Templates, em um projeto Web com Python + Google App Engine, nada mais são do que arquivos HTML acrescidos de algumas marcações específicas para representar dados dinâmicos.

<html>
    <head><title></title></head>
    <body>
        <p>Nome: {{ nome }}</p>
        <p>Email: {{ email }}</p>
        <p>URL: {{ url }}</p>
        <p>Comentário: {{ comentario }}</p>
    </body>
</html>

Observe que em meio ao HTML, temos 4 campos diferentes: {{ nome }}, {{ url }}, {{ email }} e {{ mensagem }}. Em um template, sempre que houver uma string do tipo {{ nome }}, o mecanismo de templates tentará substituir tal string pelo valor da variável nome, de forma que esse valor apareça no HTML final (depois veremos como passar valores aos templates).

Além de campos para simples substituição pelo valor de variáveis, o mecanismo de template padrão do GAE fornece construções para fazer repetição, seleção, dentre outras coisas. Veja:

<html>
    <head><title></title></head>
    <body>
        {% for m in mensagens %}
            {% if m.publico %}
                <p>Nome: {{ m.nome_usuario }}</p>
                <p>Email: {{ m.email }}</p>
                <p>URL: {{ m.url }}</p>
                <p>Comentário: {{ m.comentario }}</p>
                <hr>
            {% endif %}
        {% endfor %}
    </body>
</html>

Tudo que o template acima precisaria receber é uma variável chamada comentarios, que fosse uma sequência de objetos do tipo Comentario.

Desse modo, separamos a parte de apresentação da parte lógica do nosso projeto. O código fica mais organizado, limpo e reusável. Vamos ver agora como acoplar esse mecanismo de templates ao nosso projetinho web do post anterior.

Juntando tudo

Precisamos, antes de mais nada, salvar o nosso arquivo que contém o template em um arquivo .html. Vamos chamá-lo de modelo.html:

<html>
    <head><title></title></head>
    <body>
        <h1>Comentários</h1>
        {% for m in mensagens %}
            <hr>
            <h2>Comentário de <em>{{ m.nome_usuario }}</em></h2>
            <p><strong>Email:</strong> {{ m.email }}</p>
            <p><strong>URL:</strong> {{ m.url }}</p>
            <p><strong>Comentário:</strong> {{ m.comentario }}</p>
        {% endfor %}
        <hr>
        <form method="POST" action="/">
            Nome: <p><input type="text" name="nome_usuario"></p>
            Email: <p><input type="text" name="email"></p>
            URL: <p><input type="text" name="url"></p>
            Comentario:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>

Veja que nosso template primeiro renderiza os comentários e, no final deles, apresenta o formulário para envio de uma nova mensagem. Vamos agora reescrever o nosso método get() do post anterior. O que antes era:

def get(self):
    self.response.out.write(html)
    self.response.out.write('<ul>')
    for msg in Mensagem.all():
        self.response.out.write('<li>' + unicode(msg.comentario) + '</li>')
    self.response.out.write('</ul>')

Passará a ser:

def get(self):
    path = os.path.join(os.path.dirname(__file__), 'modelo.html')
    valores = {'mensagens': Mensagem.all()}
    self.response.out.write(template.render(path, valores))

Chamamos a função render() para gerar uma string com conteúdo HTML, com base no arquivo de template modelo.html (passado através da variável path) e do conjunto de valores a ser inserido de forma dinâmica no nosso HTML (dicionário valores). Lembra que, dentro do template, nós referenciávamos uma variável mensagens?

...    
{% for m in mensagens %}
    <hr>
    <h2>Comentário de <em>{{ m.nome_usuario }}</em></h2>
    <p><strong>Email:</strong> {{ m.email }}</p>
    <p><strong>URL:</strong> {{ m.url }}</p>
    <p><strong>Comentário:</strong> {{ m.comentario }}</p>
{% endfor %}
...

No exemplo acima, mensagens terá o conteúdo de Mensagem.all(), que é uma sequência contendo todas as mensagens existentes no datastore. Assim, esse trecho do template irá percorrer as mensagens e imprimir os valores dos campos nome_usuario, email, url e comentario nos locais correspondentes do HTML. Tudo isso é feito no lado servidor, ou seja, o navegador irá receber o HTML já pronto com os valores nos locais adequados. Um exemplo de trecho de HTML gerado no servidor poderia ser:

...
    <hr>
    <h2>Comentário de <em>Pedro Pedreira</em></h2>
    <p><strong>Email:</strong> pedro@pedreira.com</p>
    <p><strong>URL:</strong> https://pythonhelp.wordpress.com/</p>
    <p><strong>Comentário:</strong> Um comentário qualquer. Valeu!</p>
...

Onde Pedro Pedreira é o valor de {{ m.nome_usuario }}, e assim por diante, para um registro qualquer.

Caso não tenha visto o post anterior, veja o código do projeto para então aplicar as mudanças para inclusão do template, ou então siga a leitura neste post para ver o projeto completo.

O projeto completo

Nosso projeto está localizado no diretório gaetest/ e possui a seguinte estrutura:

gaetest/
    app.yaml
    handlers.py
    modelo.html

Agora vamos ver o conteúdo dos arquivos:

app.yaml:

application: gaetest
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: handlers.py

handlers.py:

import os
import webapp2
from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app


class PostHandler(webapp2.RequestHandler):

    def get(self):
        path = os.path.join(os.path.dirname(__file__), 'modelo.html')
        valores = {'mensagens': Mensagem.all()}
        self.response.out.write(template.render(path, valores))

    def post(self):
        msg = Mensagem(
            nome_usuario=self.request.get('nome_usuario'),
            url=self.request.get('url'),
            email=self.request.get('email'),
            comentario=self.request.get('comentario')
        )
        msg.put()
        self.redirect('/')


class Mensagem(db.Model):
    nome_usuario = db.StringProperty(required=True)
    url = db.LinkProperty()
    email = db.EmailProperty()
    comentario = db.TextProperty(required=True)


mapeamento = [
    ('/', PostHandler),
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

modelo.html:

<html>
    <head><title></title></head>
    <body>
        <h1>Comentários</h1>
        {% for m in mensagens %}
            <hr>
            <h2>Comentário de <em>{{ m.nome_usuario }}</em></h2>
            <p><strong>Email:</strong> {{ m.email }}</p>
            <p><strong>URL:</strong> {{ m.url }}</p>
            <p><strong>Comentário:</strong> {{ m.comentario }}</p>
        {% endfor %}
        <hr>
        <form method="POST" action="/">
            Nome: <p><input type="text" name="nome_usuario"></p>
            URL: <p><input type="text" name="url"></p>
            Email: <p><input type="text" name="email"></p>
            Comentario:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>

Executando o projeto

Salve o conteúdo acima nos arquivos correspondentes dentro da pasta gaetest e então rode o servidor local de testes. Para isso, vá até a pasta onde você descompactou o pacote na instalação do GAE e rode o seguinte comando:

./dev_appserver.py /caminho/para/o/projeto/gaetest/

Onde /caminho/para/o/projeto/gaetest/ é o caminho para o diretório onde está o projeto que queremos executar. Após isso, acesse o projeto no seu navegador através da URL localhost:8080.

Boas práticas

Boas práticas são muito importantes na hora de desenvolver um projeto de software. À medida que o projeto vai crescendo, a urgência por uma boa organização dos arquivos também cresce. É comum termos projetos com uma estrutura parecida com essa:

project/
    app.yaml
    handlers.py
    models.py
    templates/
        base.html
        form.html
        list.html

Veja que temos agora dois arquivos .py:

  • handlers.py: as classes que irão manipular nossas requisições;
  • models.py: as classes que representam os modelos de dados em nosso projeto (como a classe Mensagem no nosso exemplo);

Além disso, temos uma pasta específica para conter os templates e dentro dela ficam todos os templates que precisarmos.

Você é livre para definir a estrutura que quiser, mas é bom seguir um padrão que seja minimamente organizado.

Outros mecanismos de template

O mecanismo de template que vimos neste post é o que já vem incluso com o pacote do GAE, que por sua vez é o mecanismo do Django. Além desse, existem vários outros mecanismos que podem ser usados, como o jinja2, por exemplo.

Leia mais

Google App Engine e Datastore

Sumário

Em um post anterior, vimos como manipular no servidor os dados vindos de um formulário HTML, com Google App Engine (GAE) e Python. Porém, até agora não vimos como armazenar dados vindos do cliente em um banco de dados.

Neste post vamos incrementar a aplicação de envio de mensagens via formulários HTML que construímos em um post anterior, possibilitando que as mensagens enviadas pelos usuários sejam gravadas em um banco de dados e que sejam listadas logo abaixo do formulário na página principal. Vamos começar vendo o mecanismo para armazenamento de dados do GAE: o datastore.

Datastore

Diferentemente do que acontece quando estamos lidando diretamente com um banco de dados relacional, onde criamos as tabelas com suas propriedades no banco de dados, para então ajeitar a aplicação para que possa acessar tal banco, com o GAE nós poderemos implementar uma app inteira sem ver código SQL algum.

Para criar uma entidade no BD, basta definir uma classe contendo os atributos que desejamos que façam parte de nosso BD. Vamos criar uma entidade chamada de Mensagem, que irá armazenar os atributos relacionados às mensagens enviadas pelo usuário:

from google.appengine.ext import db

class Mensagem(db.Model):
    nome_usuario = db.StringProperty(required=True)
    url = db.LinkProperty()
    email = db.EmailProperty()
    comentario = db.TextProperty(required=True)

Nossa classe estende a classe db.Model, o que é um requisito para que ela possa representar uma entidade no BD. Veja que a definição de uma entidade é bem simples, bastando declarar os atributos desejados como variáveis de classe. Já definimos também os tipos dos dados que cada atributo irá representar. No exemplo acima, StringProperty é o tipo que usamos para definição de campos de texto com menos de 500 caracteres. Já o tipo TextProperty é usado para a definição de campos para textos mais longos. Para conhecer a lista completa dos tipos de atributos disponíveis no framework de persistência do Google App Engine, consulte a documentação oficial.

Persistência de objetos no datastore

Uma vez que definimos uma entidade para representação de nossas mensagens no banco de dados, criar e salvar dados no BD é tão simples quanto criar um objeto:

msg = Mensagem()
msg.nome_usuario = "stummjr"
msg.url = "https://pythonhelp.wordpress.com"
msg.email = "stummjr@someemail.com"
msg.comentario = "Um comentário bem interessante!!!"
msg.put()

O procedimento é bem simples. Primeiramente, instanciamos um objeto da classe Mensagem (linha 1). Após isso, setamos os atributos desse objeto (linhas 2-5). Por fim, salvamos tal objeto no BD (linha 6).

Também poderíamos passar os valores para todos os atributos como parâmetros nomeados na construção do objeto:

msg = Mensagem(
    nome_usuario="stummjr",
    url="https://pythonhelp.wordpress.com",
    email="stummjr@someemail.com",
    comentario="Um comentário bem interessante!!!"
)
msg.put()

Agora já podemos integrar o código acima em nossa app do post anterior. Basta criar o objeto Mensagem e persistir esse objeto no BD dentro do método post, que irá tratar os dados enviados pelo usuário via formulário:

def post(self):
    msg = Mensagem(
        nome_usuario=self.request.get('nome_usuario'),
        url=self.request.get('url'),
        email=self.request.get('email'),
        comentario=self.request.get('comentario')
    )
    msg.put()
    self.redirect('/')

Observe que, ao final do método post(), fizemos uma chamada ao método self.redirect('/'), que faz com que o usuário seja redirecionado para o recurso recebido como parâmetro (nesse caso, a raiz do site). Fizemos isso porque se apenas enviarmos ao usuário uma resposta de confirmação, ele poderá causar a submissão duplicada do formulário caso solicite ao navegador que este atualize a página (o famoso F5). Assim, redirecionamos a outro recurso para evitar que uma sequência de refreshes na página recebida como resposta a um POST possa causar várias inserções duplicadas.

Adicionamos os campos nome_usuario, url e email ao formulário contido na variável html:

html = """
    <html>
        <head>
            <title></title>
        </head>
        <body>
            <form method="POST" action="/">
                Nome: <p><input type="text" name="nome_usuario"></p>
                URL: <p><input type="text" name="url"></p>
                Email: <p><input type="text" name="email"></p>
                Comentário:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
                <button type="submit">Enviar</button>
            </form>
        </body>
    </html>
"""

Feito isso, agora nosso app já é capaz de salvar no BD os comentários enviados pelos usuários. Caso já tenha visto o post anterior, apenas complemente aquele código com o que foi apresentado até aqui e execute servidor de desevolvimento local. Se não viu o post anterior, o código completo é apresentado na seção “O projeto completo”.

A interface administrativa local

Enquanto estamos desenvolvendo nosso app, podemos querer verificar se determinado objeto foi realmente persistido no BD ou não. Caso ainda não tenhamos desenvolvido algum método para listagem de objetos em nosso app, podemos utilizar a interface administrativa local do GAE, que fica disponível na URL http://localhost:8080/_ah/admin/ (obs.: o servidor de testes precisa estar em execução)

http7.1

Basta selecionar o tipo de objeto e solicitar a listagem.

Consultas ao datastore

Para realizar consultas aos objetos que estão no banco de dados, basta utilizar os métodos all(), filter(), dentre outros que são comuns a todas as classes que estendem db.Model. Veja alguns exemplos:

# obtém todos os registros de Mensagem do BD
msgs = Mensagem.all()

# pega somente Mensagens do "John Doe"
msgs.filter('nome_usuario =', "John Doe")

# ordena pelo endereço do email, de forma DESC (-)
msgs.order('-email')

for msg in msgs:
    print msg.email

Veja mais métodos para consulta de dados na documentação oficial.

Em nosso projeto, precisamos apenas adicionar ao método get() a funcionalidade de listagem das mensagens já existentes no BD, logo abaixo do formulário de submissão de mensagens. Com base nos métodos de consulta que vimos, podemos montar nosso método:

def get(self):
    self.response.out.write(html)
    self.response.out.write('<ul>')
    for msg in Mensagem.all():
        self.response.out.write('<li>' + unicode(msg.comentario) + '</li>')
    self.response.out.write('</ul>')

Tenha em mente que imprimir código HTML diretamente não é uma boa abordagem para desenvolvimento web. Existem técnicas mais recomendadas, como a utilização de modelos de páginas. Mas isso é um assunto para um outro post.

O projeto completo

Como já vimos anteriormente, a estrutura do diretório do nosso projeto deve ficar como:


projeto/
  handlers.py
  app.yaml

Segue abaixo o código dos arquivos relacionados:

Arquivo handlers.py:

import webapp2
from google.appengine.ext import db
from google.appengine.ext.webapp.util import run_wsgi_app

html = """
    <html>
        <head>
            <title></title>
        </head>
        <body>
            <form method="POST" action="/">
                Nome: <p><input type="text" name="nome_usuario"></p>
                URL: <p><input type="text" name="url"></p>
                Email: <p><input type="text" name="email"></p>
                Comentário:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
                <button type="submit">Enviar</button>
            </form>
        </body>
    </html>
"""

class PostHandler(webapp2.RequestHandler):

    def get(self):
        self.response.out.write(html)
        self.response.out.write('<ul>')
        for msg in Mensagem.all():
            self.response.out.write('<li>' + unicode(msg.comentario) + '</li>')
        self.response.out.write('</ul>')

    def post(self):
        msg = Mensagem(
            nome_usuario=self.request.get('nome_usuario'),
            url=self.request.get('url'),
            email=self.request.get('email'),
            comentario=self.request.get('comentario')
        )
        msg.put()
        self.redirect('/')


class Mensagem(db.Model):
    nome_usuario = db.StringProperty(required=True)
    url = db.LinkProperty()
    email = db.EmailProperty()
    comentario = db.TextProperty(required=True)


mapeamento = [
    ('/', PostHandler),
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

Arquivo app.yaml:


application: gaepost
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: handlers.py

Com os arquivos acima, você já pode executar o projeto usando o ambiente de testes.

Aprenda mais

Caso tenha dificuldades para entender o funcionamento do Google App Engine, leia os posts anteriores nos quais introduzimos o assunto:

  1. Desenvolvendo com Python e Google App Engine
  2. Tratando dados de formulários com Google App Engine

Se quiser ir mais a fundo no GAE, sugiro que leia a documentação oficial, e também o livro Programming Google App Engine.

Função zip em Python

zipper

Aviso aos navegantes: a função zip, apesar do seu nome, não serve para zipar um arquivo. Se estiver procurando por isso, veja o módulo zipfile.


Escreva em Python uma função que, dadas duas listas não vazias e de mesmo comprimento, retorne como resultado uma nova lista contendo em cada posição a soma dos elementos correspondentes nas duas listas recebidas. Exemplo:

def soma_listas(l1, l2):
    # seu código aqui
    # ...
    return l3

l1 = [1, 2, 3]
l2 = [4, 5, 6]
print soma_listas(l1, l2) # deve imprimir [5, 7, 9]

Uma ideia que pode surgir é:

def soma_listas(l1, l2):
    l3 = []
    for i in range(0, len(l1)):
        l3.append(l1[i] + l2[i])
    return l3

Isso para quem ainda não conhece a função builtin zip. A documentação da função diz (tradução própria):

Esta função retorna uma lista de tuplas, onde a i-ésima tupla contém o i-ésimo elemento de cada um dos argumentos.

O exemplo usado na documentação oficial é bem claro:

>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> print zip(x, y)
[(1, 4), (2, 5), (3, 6)]

O que a função fez foi: para cada elemento da lista x, juntou o elemento correspondente em posição da lista y a ele em uma tupla e a adicionou a uma lista.


[ 1, 2, 3 ]
-zip--------------- ====> [(1, 4), (2, 5), (3, 6)]
[ 4, 5, 6 ]

Sacou por que o post inicia com a imagem de um zíper?

Isso é particularmente útil quando precisarmos percorrer duas sequências de elementos em paralelo. A função soma_listas, usando zip, fica:

def soma_listas(l1, l2):
    l3 = []
    for x in zip(l1, l2):
        l3.append(x[0]+x[1])
    return l3

Ou então, usando list comprehensions:

def soma_listas(l1, l2):
    return [x[0]+x[1] for x in zip(l1, l2)]

Para finalizar, vamos escrever agora uma funçãozinha que receba duas listas como entrada e retorne como resultado uma nova lista contendo, em cada posição, o maior valor da posição correspondente nas duas listas. Por exemplo:

maiores([1, 2, 9], [2, 1, 7]) -> [2, 2, 9]

Com a função zip, fica barbada:

def maiores(l1, l2):
    l3 = []
    for x, y in zip(l1, l2):
        l3.append(x if x > y else y)
    return l3

Como a função zip retorna uma lista de tuplas de 2 valores, podemos desempacotar essas tuplas diretamente em x e y no próprio for, assim como fizemos no código acima. (for x, y in zip(l1, l2))

Obs.: Caso não tenha entendido o código l3.append(x if x > y else y), leia também o post “Seleção Condicional de Valores”.

Tratando dados de formulários com Google App Engine

http6.0Os formulários HTML são uma das formas mais usadas para passagem de dados dos usuários para os servidores na web. Você, usuário, quantas vezes por dia preenche algum formulário e clica em um botãozinho próximo a ele? Se você faz uma busca no google.com, você preenche um campo de entrada de um formulário e clica no botão “Buscar”. Quando envia um tweet ou uma mensagem no Facebook, você preenche campos de entrada de um formulário e os submete a um servidor. Quando faz login no GMail, você preenche dois campos de entrada (usuário e senha) e então clica no botão “Login” para submeter os dados ao servidor para que este verifique suas credenciais. Ou seja, você usa formulários pra caramba!

Neste post rápido daremos sequência ao post anterior, vendo como implementar na prática o tratamento de dados vindos de formulários em um app web usando Python e Google App Engine (GAE). (caso você não conheça o Google App Engine, leia antes o post anterior)

POSTando dados na web

O objetivo é criar uma aplicaçãozinha web bem simples na qual o usuário possa escrever uma mensagem em um formulário e receber de volta essa mensagem no navegador. Vamos criar esse app do mesmo modo que fizemos no post anterior. Crie um diretório mural e dentro dele crie os arquivos app.yaml (que vai conter as configurações) e handlers.py (que vai conter o código Python para manipular as requisições).

app.yaml:

application: mural
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: handlers.py

O código acima apresentado é o conteúdo do arquivo de configurações app.yaml, que define alguns parâmetros para o projeto.
O manipulador de requisições (handlers.py) deverá ser implementado da seguinte forma:

  1. Ao receber uma requisição GET, este deve retornar um HTML contendo um formulário para que o usuário escreva sua mensagen;
  2. Ao receber uma requisição POST (vinda do formulário recebido pelo usuário quando executa o GET), este deve apresentar uma página HTML contendo o texto submetido pelo usuário.

Para simplificar a nossa implementação, vamos colocar o seguinte código HTML dentro de uma string no nosso código Python:

<html>
    <head><title></title></head>
    <body>
        <form method="POST" action="/">
            <textarea name="comentario" cols="40" rows="10">
            </textarea>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>

Faremos isso para que, ao receber uma requisição do tipo GET, nosso appzinho possa simplesmente gravar em self.response.out a string contendo o código acima, fazendo com o formulário HTML seja passado como resposta à requisição GET do cliente. (você já sabe, do post anterior, que ao escrever um valor qualquer em self.response.out estamos adicionando esse valor ao corpo da mensagem de resposta)

Segue abaixo o código de nosso app:

import webapp2
from google.appengine.ext.webapp.util import run_wsgi_app


html = """
<html>
    <head><title></title></head>
    <body>
        <form method="POST" action="/">
            <textarea name="comentario" cols="40" rows="10">
            </textarea>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>
"""

class PostHandler(webapp2.RequestHandler):
    def getself):
        self.response.out.write(html)

    def post(self):
        comentario = self.request.get('comentario')
        self.response.out.write(comentario)


mapeamento = [
    ('/', PostHandler),
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

Observe que a escrita do formulário HTML como resposta ao cliente ocorre dentro do método get(), que é chamado pelo ambiente de suporte sempre que houver uma requisição GET para /. No form temos dois atributos:

  1. method="POST": indica o método HTTP a ser usado para transmitir os dados ao servidor quando o usuário clicar no botão Enviar;
  2. action="/": indica para qual recurso serão enviados os dados do form quando o usuário submeter as informações clicando no botão.

Ou seja, após preencher o campo comentario e clicar no botão Enviar, o usuário faz com que o navegador envie uma mensagem HTTP POST ao recurso / do servidor. Essa mensagem contém, além do cabeçalho, a variável comentario com o conteúdo escrito pelo usuário nesse campo.

Como todas as requisições ao recurso / são tratadas por instâncias de PostHandler (veja a variável mapeamento), definimos em PostHandler um método chamado post() para lidar com requisições HTTP POST a esse recurso. Assim como ocorre para o método get(), o framework que estamos usando (webapp2) define que requisições do tipo POST serão tratadas por um método chamado post() implementado na classe responsável por manipular as requisições.

Sabendo disso, agora observe nosso método post() (chamado pelo ambiente de suporte quando for recebida uma requisição POST).

def post(self):
    comentario = self.request.get('comentario')
    self.response.out.write(comentario)

Veja que temos um atributo self.request que contém os campos enviados pelo usuário e que os valores desses campos podem ser acessados através de self.request.get('nome_do_campo').

Para simplificar nosso app, estamos apenas ecoando a mensagem enviada pelo usuário, de forma que após clicar em Enviar, este receba de volta a mensagem que enviou ao servidor (self.response.out.write(comentario)).

Agora, vamos testar o nosso app.

Testando o app

Para testar o app recém desenvolvido, siga os passos descritos no post anterior, trocando apenas o nome do diretório para o nome do diretório do projeto que criamos neste post.

GET x POST

No código do nosso appzinho, o manipulador trata dois tipos de requisições HTTP: GET e POST. Vamos ver rapidamente as diferenças entre elas.

GET

Método usado geralmente para envio de requisições que não resultem em alterações no servidor. Como o nome já diz, é indicado para a obtenção de dados.

Os parâmetros são passados para o servidor através da própria URL:

http://www.google.com/search?q=teste

No exemplo acima, passamos a variável q com o valor teste para o recurso /search, de forma que o servidor da Google vai acessar essa variável e realizar a busca usando o valor dela como filtro.

POST

Método usado quando a requisição causa alterações no servidor, como em uma requisição que envia dados a serem gravados num banco de dados. Diferentemente do método GET, quando usamos o POST em uma requisição HTTP, os parâmetros não são codificados na URL, mas sim no corpo da mensagem HTTP a ser enviada ao servidor.

Atenção

Deve ficar claro que a forma como armazenamos o conteúdo HTML do formulário (em uma variável no código) não é a melhor solução. Fizemos isso com a única finalidade de simplificar nosso exemplo. Em projetos “de verdade”, normalmente usamos um mecanismo para renderização de templates, que vem incluso com a maioria dos frameworks web. Se quiser saber mais, veja a documentação oficial do GAE sobre templates.

Sabendo como tratar dados recebidos do usuário e como manipular requisições GET e POST, você já pode começar a fazer projetinhos maiores. Vá em frente! 🙂

Desenvolvendo com Python e Google App Engine

Importante: a documentação oficial do Google App Engine aqui referenciada está disponível em português. Basta selecionar o idioma no canto inferior direito, caso esteja em outro idioma.

Sumário do post:

No último post nós vimos como desenvolver um serviço web bem simples usando Python e CGI. Mas, conforme vimos no próprio post, CGI não é o mecanismo mais adequado para dar suporte a um aplicativo web.

Do seu início até final da década de 90, a web era usada principalmente para a publicação de conteúdo em HTML. Porém, algum tempo depois ela passou a ser usada também como um ambiente para execução de aplicações completas.

O crescimento das aplicações possibilitou a observação de que boa parte dos projetos web tinham alguns componentes básicos em comum, gerando uma grande quantidade de retrabalho a cada novo projeto, o que acarretaria um alto custo para cada projeto, e também aumentaria as chances de o sistema apresentar defeitos. Visando eliminar boa parte do retrabalho, foram criados os frameworks de desenvolvimento, que fornecem vários serviços básicos para serem reutilizados pela aplicação, de forma que esta não precise reimplementá-los. (obs.: framework poderia ser traduzido como arcabouço)

A segurança de um aplicativo é um dos aspectos para os quais os frameworks possuem um conjunto de serviços para oferecer ao desenvolvedor. Isso é muito importante, pois não é todo programador que sabe como lidar com aspectos de segurança de uma forma adequada. Gerenciamento do banco de dados é outro serviço comumente oferecidos por frameworks para desenvolvimento web. Enfim, eles oferecem um ferramental bem interessante para que o desenvolvedor de uma aplicação web se preocupe principalmente com as funcionalidades e com a lógica de negócio do seu aplicativo, deixando um pouco “de lado” alguns aspectos que ficam sob responsabilidade do framework.

Alguns dos principais frameworks para desenvolvimento web em Python são:

O que nós vamos utilizar neste post não é apenas um framework, mas sim um conjunto completo para desenvolvimento, implantação e execução de aplicativos web, fornecido pela Google: o Google App Engine. Neste post, iremos apresentar o desenvolvimento de um aplicativozinho web que retorna alguma frase famosa para o usuário, algo como a frase do dia. Mas antes disso, vamos ver do que se trata o Google App Engine.

O Google App Engine

gaelogo

O Google App Engine (GAE) é uma plataforma para desenvolvimento de aplicativos web para serem hospedados na infraestrutura da Google. Além de oferecer uma interface bem interessante para gerenciamento dos aplicativos web (veja na figura abaixo), o GAE fornece de lambuja o balanceamento de carga da aplicação, espalhando a execução dela por vários servidores se assim for necessário, e alocando automaticamente mais recursos para a aplicação que estiver rodando nessa infraestrutura. Além da disponibilizar e gerenciar a infraestrura para o desenvolvedor, o GAE ainda provê uma série de facilidades para o desenvolvimento, como um framework para persistência de dados, para tratamento de requisições, para caching de dados, dentre outras coisas legais. E, pra ficar mais interessante ainda, é gratuito para quem quiser testar ou desenvolver aplicativos sem maiores pretensões.

http.5.1

Painel de controle do GAE

Bom, chega de propaganda e vamos ao que interessa!

Instalando e configurando o GAE

Como já foi comentado, a hospedagem de aplicativos escritos para o GAE fica por conta da Google, mas para podermos testar localmente nosso app, é preciso que instalemos um ambiente próprio para isso. Siga os passos abaixo:

  1. Baixe o arquivo correspondente à sua plataforma clicando aqui;
  2. Descompacte o arquivo acima no diretório de sua preferência;

Mais informações sobre a instalação do ambiente podem ser encontradas aqui.

Desenvolvendo nosso app

Vamos começar agora o desenvolvimento de nosso aplicativo de frase do dia. A primeira coisa a fazer é criar um diretório onde nosso app ficará localizado. Podemos chamar tal diretório de frasedodia, ou o nome que você achar mais conveniente. Dentro do diretório recém criado, crie um arquivo chamado frasedodia.py e outro chamado app.yaml. O primeiro é a nossa aplicação em si e o último é o arquivo que irá conter as configurações do nosso projeto.
A estrutura do nosso projeto ficará assim:

frasedodia/
  frasedodia.py
  app.yaml

Agora, precisamos escrever as configurações do nosso projeto. Para isso, cole o seguinte conteúdo dentro do arquivo app.yaml:

application: frasedodia
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: frasedodia.py

O mais importante a entender neste momento é a opção handlers. Dentro dela, temos um item chamado url com valor /.* e um atributo script com o nome do nosso arquivo .py como valor. Essa configuração significa que requisições para qualquer recurso dentro do projeto (/.*) serão encaminhadas para o arquivo frasedodia.py. Se por acaso quiséssemos que somente as requisições para os recursos contidos em uma pasta app fossem encaminhadas ao arquivo handlers.py, faríamos o seguinte:

handlers:
- url: /app/.*
  script: handlers.py

Assim, uma requisição para frasedodia.appspot.com/app/qualquercoisa seria encaminhada para o script definido na configuração acima (handlers.py). Agora vamos nos concentrar no código que irá atender às requisições do usuário.

Manipulador de requisições

Se quisermos tomar proveito das vantagens que o GAE nos fornece, é interessante que não usemos CGI. Vamos utilizar um framework para web bem simples chamado webapp2 que já é fornecido juntamente com o GAE.

O primeiro passo que devemos realizar é definir quais classes terão suas instâncias responsáveis por manipular requisições para determinadas URLs. Uma vez que já definimos que todas as requisições para o aplicativo serão direcionadas a frasedodia.py, devemos agora definir, dentro desse arquivo, o mapeamento de URLs para classes. Insira o código a seguir em frasedodia.py:

import webapp2
from google.appengine.ext.webapp.util import run_wsgi_app

mapeamento = [
    ('/', FraseDoDia)
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

No código acima, definimos que toda requisição para a raiz de nosso app será tratada por instâncias da classe FraseDoDia. Mas, a classe FraseDoDia não é uma classe qualquer. Ela deve obrigatoriamente estender a classe webapp2.RequestHandler, para que suas instâncias sejam também instâncias de RequestHandler. Nosso app irá criar uma instância da classe FraseDoDia e irá chamar nessa instância o método apropriado para tratar a requisição (get() para o caso de requisição GET ou post() para o caso de requisição POST). Esse método irá tratar a requisição e devolver uma resposta ao cliente. Tudo isso é feito automagicamente pelo framework de suporte, ficando ao nosso cargo apenas criar as classes que irão ser responsáveis por tratar as requisições.

Objetos do tipo RequestHandler possuem dois atributos muito importantes para o desenvolvedor:

  • self.request: é uma instância de Request, contendo a requisição recebida do usuário;
  • self.response: é uma instância de Response, contendo a mensagem a ser enviada como resposta à requisição do usuário.

Dessa forma, instâncias da classe RequestHandler devem fazer o seguinte, a cada requisição recebida:

  1. Tratar os dados recebidos através do objeto self.request;
  2. Gravar os dados a serem enviados ao usuário no objeto self.response.

Assim, devemos então implementar nossa classe FraseDoDia como uma subclasse de RequestHandler e nela implementar o método get() para retornar a frase para o cliente. Vamos então adicionar o código da classe ao nosso arquivo frasedodia.py:

import webapp2
from google.appengine.ext.webapp.util import run_wsgi_app

class FraseDoDia(webapp2.RequestHandler):

    def get(self):
        self.response.headers['Content-Type'] = 'text/json'
        self.response.out.write('{"frase": "Uma frase qualquer!"}')

mapeamento = [
    ('/', FraseDoDia)
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

Vendo o código acima, dá pra entender que quando ocorre uma requisição GET para o recurso / de nossa aplicação, um objeto do tipo FraseDoDia será designado pelo framework de suporte para tratar tal requisição, executando o método get(). Esse método, por sua vez, faz duas coisas:

  1. Adiciona ao cabeçalho da resposta HTTP — self.response.headers — o tipo do conteúdo a ser enviado como resposta ao cliente (text/json), de forma que o navegador saiba o que fazer com o conteúdo recebido;
  2. Escreve no corpo da resposta HTTP — self.response — o conteúdo a ser enviado ao cliente, que são dados representados em formato JSON.

Tudo o que precisamos fazer é implementar a classe que vai tratar das requisições à determinadas URLs, sem a necessidade de chamá-las nem nada. Quem faz isso é o ambiente de suporte provido pelo framework webapp.

Tendo implementado uma versão básica de nosso serviço, vamos colocar nosso ambiente de testes em funcionamento.

Executando o ambiente de testes

Vá até o local onde você extraiu o ambiente do GAE em sua máquina. Dentro dessa pasta, execute o seguinte comando:

./dev_appserver.py /caminho/para/o/projeto/frasedodia/

O caminho mostrado acima, passado como argumento para o programa dev_appserver.py, é referente ao diretório onde criamos os arquivos app.yaml e frasedodia.py.

O comando acima iniciou o servidor local para desenvolvimento e testes na porta 8080. Podemos acessar o serviço através da URL: http://localhost:8080/. Ao acessar essa URL, você terá como resposta o conteúdo JSON retornado pelo nosso serviço Web:

{"frase": "Uma frase qualquer!"}

Por enquanto estamos retornando apenas uma frase constante, e a idéia do serviço é retornar uma frase aleatória. Para tanto, temos algumas opções:

  • Manter algumas strings em memória em uma lista;
  • Manter registros no banco de dados e buscar um ao receber uma requisição.

Por fins de simplicidade, vamos seguir a primeira opção. Vamos adicionar uma nova classe chamada FraseAleatoriaDoDia, implementar o método get(), já que essa classe vai estender RequestHandler, e adicionar a nova classe no mapeamento de URLs, relacionado a URL /random. Veja o código completo abaixo:

import random
import webapp2
from google.appengine.ext.webapp.util import run_wsgi_app

class FraseDoDia(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/json'
        self.response.out.write('{"frase": "Uma frase qualquer!"}')

class FraseAleatoriaDoDia(webapp2.RequestHandler):
    frases = [
        ('Insanity: doing the same thing over and over again and expecting different results', 'Albert Einstein'),
        ('The world is a dangerous place to live; not because of the people who are evil, but because of the people who don\'t do anything about it.', 'Albert Einstein'),
        ('A person who never made a mistake never tried anything new.', 'Albert Einstein'),
        ('Love all, trust a few, do wrong to none.', 'William Shakespeare'),
        ('A fool thinks himself to be wise, but a wise man knows himself to be a fool.', 'William Shakespeare'),
        ('Darkness cannot drive out darkness; only light can do that. Hate cannot drive out hate; only love can do that.', 'Martin Luther King, Jr.')
    ]

    def get(self):
        self.response.headers['Content-Type'] = 'text/json'
        i = random.randint(0, len(self.frases)-1)
        self.response.out.write('{"frase": "%s", "autor": "%s"}' % (self.frases[i][0], self.frases[i][1]))

mapeamento = [
    ('/', FraseDoDia),
    ('/random', FraseAleatoriaDoDia),
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

Rode o servidor de aplicação local com./dev_appserver.py /caminho/para/o/projeto/frasedodia/ e acesse http://localhost:8080/random para obter uma das frases.

Com o app funcionando, agora você pode registrar e fazer upload do mesmo para os servidores da Google, se assim quiser. Se desejar colocar em produção o app que implementamos aqui, siga as instruções contidas na documentação oficial, na seção que trata sobre o upload de um app.

Depois de fazer o upload de seu app, você poderá acessá-lo através de uma URL dentro de .appspot.com, como: http://id_da_aplicacao.appspot.com. Para ver informações sobre a sua aplicação, você pode acessar o painel de controle da sua conta no GAE através do endereço: appengine.google.com/.

O Google App Engine é uma ótima opção para dar suporte a uma aplicação web, visto que com ele eliminamos a necessidade de contratar um plano de hospedagem, de gerenciar o servidor web, de cuidar do balanceamento de carga, etc. Vale a pena dar uma estudada mais a fundo no assunto. 🙂

Aprenda mais

Programando para a Web em Python

Sumário

Este é o quarto post da série sobre Python + Web. Nós já vimos como fazer requisições HTTP a partir de um programa Python, como fazer extração de dados de conteúdo HTML obtido via HTTP e como lidar com dados em formato JSON retornados por um serviço Web. Porém, até agora trabalhamos somente no lado cliente, obtendo dados via HTTP para então fazer algo com eles. A partir de agora vamos para o lado do servidor, onde iremos escrever o provedor do serviço.

Neste post, veremos como construir um servicinho web bem simples, que retorne para o cliente o dia da semana relativo a determinada data, codificado em formato JSON.

Web em Python

Python é uma linguagem muito versátil, podendo ser usada para vários fins. Um dos nichos em que Python mais tem sido usada ultimamente é no desenvolvimento web. E quando se trata de web, temos várias ferramentas disponíveis para usar com Python. Este post irá apresentar a forma mais simples de todas: o CGI (Common Gateway Interface).

CGI

O funcionamento do CGI é bem simples: ao receber uma requisição, o servidor web roda um programa executável para gerar o conteúdo a ser retornado para o cliente (é esse programinha executável que vamos implementar). Assim, um programinha bem simples que poderia funcionar juntamente com um servidor CGI é:

#!/usr/bin/python

# cabecalho que informa o browser para renderizar como HTML
print "Content-Type: text/html\n\n"
# o conteudo em si
print "<strong>Hello, world!</strong>"

Se você quiser brincar um pouquinho com CGI, siga os seguintes passos:

  1. Crie uma pasta chamada src e dentro dela outra pasta chamada cgi-bin;
  2. Salve o arquivo acima como hello.py dentro da pasta cgi-bin recém criada;
  3. Dê permissão de execução ao arquivo hello.py: chmod +x hello.py;
  4. Pelo shell de comandos do seu sistema operacional, vá até a pasta src e execute o servidorzinho CGI que Python traz consigo: python -m CGIHTTPServer 8000
  5. Abra um navegador e acesse a URL: http://localhost:8000/cgi-bin/hello.py
  6. Como resultado, você deverá ver a mensagem “Hello, world!” em negrito no seu navegador.

Mas, para que faça sentido, é preciso que nosso aplicativo seja capaz de obter eventuais informações que o usuário passar a ele. Usando o módulo cgi, podemos manipular parâmetros fornecidos pelo usuário, dentre outras possibilidades. Veja o exemplo abaixo:

#!/usr/bin/python
import cgi

# obtem referencia as variaveis passadas pelo cliente
fields = cgi.FieldStorage()

# cabecalho que informa o browser para renderizar como HTML
print "Content-Type: text/html\n\n"

nome = fields.getvalue('nome')
print "<strong>Hello, %s!</strong>" % (nome)

Salve o arquivo acima no mesmo diretório cgi-bin com o nome de hellouser.py, dê permissão de execução para o mesmo (chmod +x hellouser.py) e então acesse no seu browser: http://localhost:8000/hellouser.py?nome=Seu Nome Aqui.

Como você deve saber, /hellouser.py?nome=Seu Nome Aqui significa que estamos passando para o “programa” /hellouser.py a variável nome com o valor Seu Nome Aqui, tudo pela URL, através do método HTTP GET.

Preparando a implementação do serviço

Antes de sairmos implementando detalhes do serviço web que nos comprometemos a implementar, vamos criar uma funçãozinha que será parte essencial do nosso projeto. Essa função irá receber 3 valores: dia, mês e ano, e irá retornar o dia da semana em forma de string. Por exemplo:

dia_da_semana(27, 3, 2013) --retorna--> "Quarta-feira"
dia_da_semana(24, 3, 2013) --retorna--> "Domingo"

Para implementá-la, vamos usar o método weekday() dos objetos date, disponível no módulo datetime. A documentação dessa função diz o seguinte (tradução própria):

Retorna o dia da semana como um inteiro, onde Segunda-feira é 0 e Domingo é 6.

Assim, tudo o que teremos que fazer é construir uma instância de date com os dados fornecidos como parâmetro para nossa função, e invocar o método weekday() nesse objeto. Exemplo:

>>> from datetime import date
>>> d = date(2013, 3, 27)
>>> print d.weekday()
2

2 significa que a data em questão, 27/03/2013 cai numa Quarta-feira, visto que Segunda-feira é 0 e que Domingo é 1. Perceba que para construir um objeto do tipo date, nós passamos as informações sobre dia, mês e ano em ordem inversa à que estamos acostumados no Brasil. A assinatura do método construtor de objetos date é:

date(ano, mês, dia)

Perfeito, agora só falta definirmos uma forma de obtermos os nomes dos dias em língua portuguesa. Vamos usar uma abordagem bem simples, mas que resolve nosso problema. Vamos usar uma lista contendo as strings representando os dias da semana. (claro que se fôssemos dar suporte a usuários de vários países, a história seria diferente)

from datetime import date

dias = [
    'Segunda-feira',
    'Terça-feira',
    'Quarta-feira',
    'Quinta-feira',
    'Sexta-feira',
    'Sábado',
    'Domingo'
]

def dia_da_semana(dia, mes, ano):
    try:
        return dias[date(ano, mes, dia).weekday()]
    except ValueError:
        return 'Erro: data mal-formatada.'

Simples, huh? Utilizamos o inteiro retornado pelo método weekday() como índice para nossa lista com os nomes dos dias da semana. Além disso, envolvemos nosso código com try-except pois o usuário pode muito bem fornecer uma data inválida, como 31/02/2013 ou 100/123/2013. O construtor date() levanta a exceção ValueError quando recebe parâmetros que não representam uma data válida.

Feito isso, agora podemos implementar nosso serviço. (leia atentamente o código acima, caso não tenha entendido algo)

Implementando o serviço web

Já vimos como implementar um aplicativozinho web beeeeeeeem simples usando CGI, também já vimos como obter o dia da semana em que determinada data cai, e num post anterior vimos como codificar e decodificar dados em formato JSON. Agora, basta juntarmos as peças para implementar o serviço.

Só para relembrar, estamos implementando um serviço web que retorna o dia da semana relativo à determinada data fornecida pelo usuário. O usuário irá fornecer a data no formato dd/mm/aaaa. Por exemplo, o usuário pode solicitar a data fazendo uma requisição ao serviço da seguinte forma: http://um.host.qualquer.com/diasemana.py?data=27/3/2013.

Segue o código do nosso serviço:

#!/usr/bin/python
# -*- encoding:utf-8 -*-
import cgi
import json
from datetime import date

dias = [
    'Segunda-feira',
    'Terça-feira',
    'Quarta-feira',
    'Quinta-feira',
    'Sexta-feira',
    'Sábado',
    'Domingo'
]

def quebra_data(data):
    if (data.count('/') == 2 and
            all((x.isdigit() for x in data.split('/')))):
        return [int(x) for x in data.split('/')]
    else:
        raise ValueError

def dia_da_semana(data):
    try:
        dia, mes, ano = quebra_data(data)
        return dias[date(ano, mes, dia).weekday()]
    except ValueError:
        return 'Erro: data mal-formatada.'

print "Content-Type: text/json\n\n"
fields = cgi.FieldStorage()
data = fields.getvalue('data')
dia_str = dia_da_semana(data) if data is not None else 'Erro'
print json.dumps({'data': data, 'diasemana': dia_str})

Como a data fornecida pelo usuário é recebida no programa através do cgi.FieldStorage() sob a forma de string, é preciso que separemos essa data em dia, mês e ano. Para isso, criamos a função quebra_data() que antes de mais nada verifica se a data fornecida pelo usuário está no formato adequado (dia/mês/ano), e caso não esteja, a função levanta uma exceção ValueError para ser capturada pela função dia_da_semana(). Se a data estiver no formato correto, é retornada uma lista com três valores inteiros: dia, mês e ano. O método split(), usado em nossa função quebra_data separa a string em questão em todas as ocorrências do caractere separador fornecido como entrada ('/') e retorna uma lista contendo as substrings.

Ao final de nosso programinha, geramos um dicionário com as informações a serem codificadas em JSON e então imprimimos a informação JSONificada.

Momento “Lição de Vida”

Como você pode perceber lendo o código acima, boa parte da lógica do programa envolve a verificação dos dados fornecidos pelo usuário. Em se tratando de serviços ou aplicativos web, nunca podemos ser relapsos com os dados fornecidos pelo usuário, afinal o sistema está exposto pela web, podendo ser acessado de qualquer lugar do mundo, por pessoas com os mais variados níveis de conhecimento e com as mais variadas intenções. Às vezes, a falta de validação de um simples dado fornecido pelo usuário pode ser o suficiente para que o usuário destrua o nosso serviço ou, o que muitas vezes é pior, obtenha acesso à informações que deveriam ser sigilosas.

Portanto, nunca deixe de verificar todo e qualquer dado que um usuário forneça para o seu sistema. Às vezes ficamos com preguiça, com pena de poluir o código com “todas aquelas validações”, ou até achando que “os nossos usuários nunca tentariam sacanear o sistema”, mas não caia na bobagem de seguir esses pensamentos. Existem várias histórias de sistemas que foram destruídos e de pessoas que perderam seus empregos por deixar de filtrar os dados fornecidos pelo usuário.

Python + CGI está na crista da onda, então?

Não. Programação web com CGI nada mais é do que escrevermos programas que serão executados pelo servidor web quando houver uma requisição para eles. Essa simplicidade toda tem o seu custo: a cada requisição recebida pelo servidor web, ele inicia um novo processo que irá executar o código do recurso solicitado. Assim, recursos que de outra forma poderiam ser compartilhados entre instâncias são perdidos ao fim de cada processo. Conexões com o banco de dados, por exemplo, são reestabelecidas a CADA requisição feita para o serviço quando usamos CGI. Ou seja, sistemas web baseados em CGI dificilmente irão escalar quando necessário for.

Além disso, programar somente com CGI não é muito prático, pois temos que nos preocupar com diversos elementos que podem ser abstraídos e passados para uma outra camada. E outra coisa, código Python imprimindo HTML é muito anos 90! 😉

Para facilitar a nossa vida e resolver alguns dos problemas acima descritos, existem os frameworks para desenvolvimento web, que serão assunto para um próximo post.

Então CGI é inútil?

Não, longe disso. Com CGI, podemos executar programas remotamente e, de lambuja, ver o resultado da execução desse programa em nosso navegador. Se ele gerar HTML na saída, melhor ainda! 🙂

MAS, é claro que não vamos deixar exposto na web um programa que apaga todos os arquivos do disco rígido do servidor em que está rodando. 😛 Só que nada impede que disponibilizemos um scriptzinho inocente via CGI para que outra pessoa possa o acessá-lo remotamente, sem a necessidade de possuir acesso SSH àquela máquina. O legal é que tudo que é preciso para o acesso é um web browser, e, como você sabe, hoje em dia até as TVs possuem web browsers, sem falar nos telefones celulares.