Tekton
“Você deve renunciar toda superficialidade, toda convenção, toda presunção e desilusão.” 1
- Gustav Mahler
Introdução
Convenção em vez de Configuração (Convention over Configuration) é o grande mantra desse capítulo. Será explicado o funcionamento do framework Tekton e todas suas convenções. A ideia principal é evitar a excessiva configuração do sistema, permitindo o foco em funcionalidades.
Diferente da abordagem do capítulo anterior, Webapp2, o roteamento e recebimento de parâmetros será feito através de convenções. Apesar disso, o conhecimento sobre os objetos básicos, Request, Response e RequestHandler, será fundamental.
Tekton será a biblioteca base. Nela serão construídos todos os exemplos de código no restante do livro.
Setup inicial
O repositório do Tekton consiste em um template para iniciar um novo projeto. É possível copiar esse código baixando o arquivo zipado. Após a extração, ele apresenta a seguinte estrutura de pastas:
tekton-master
└── backend
├── appengine
├── apps
├── build_*script*s
├── test
└── venv
A seguir são apresentadas breves descrições de cada um dos diretórios:
- backend: diretório raiz de todo código do servidor;
- appengine: aqui se encontram todos arquivos de configuração e integração com o GAE;
- apps: contém as aplicações, com suas lógicas de negócio, que compõem o sistema;
- build_scripts: possui scripts para geração de produtos, como arquivos de internacioanalização;
- test: diretório com scripts de testes;
- venv: pasta que define o ambiente virtual do sistema.
Os detalhes sobre essas estruturas serão apresentados nas próximas seções.
Virtualenv
Virtualenv é uma biblioteca Python. Ela permite que se crie um ambiente isolado para cada projeto. É possível então definir qual a versão da linguagem e quais são as bibliotecas externas que serão utilizadas.
Instruções de instalação para os sistemas Linux, Mac OS e Windows.
Virtualenv Linux e Mac OS
A instalação do virtualenv no Linux pode ser feita através do comando sudo apt-get install python-virtualenv, sendo necessária a senha de root para instalação.
Já no Mac OS é necessário instalar a biblioteca utilizando o instalador de pacotes Python (pip).
Para isso, basta instalar a versão 2.7 com o [Homebrew](http://docs.python-guide.org/en/latest/starting/install/osx/: brew install python.
Essa versão já inclui o pip em sua instalação. O comando pip install virtualenv deve então ser utilizado para instalar o virtualenv.
Depois de instalado o virtualenv, o script de setup do projeto é o mesmo para Linux e Mac.
A pasta venv do projeto contém o script venv.sh.
Ele cria o ambiente isolado e instala as bibliotecas necessárias. Um exemplo de execução do script é exibido a seguir:
Execução do script venv.sh
~/PycharmProjects/appengineepython$cd backend/venv/
~/PycharmProjects/appengineepython/backend/venv$./venv.sh
Como as dependências necessárias são baixadas da internet, é necessária conexão com a internet.
Virtualenv Windows
Para instalar o virtualenv no Windows se faz necessário a instalação do pip, uma biblioteca python para instalação de pacotes. Para isso deve ser baixado o script Python get_pip.py. Depois o script deve ser executado com o interpretador Python, conforme exemplo a seguir:
Execução do script get_pip.sh
C:\Users\renzovm>cd Desktop
C:\Users\renzovm\Desktop>python get_pip.py
Downloading/unpacking pip
Downloading/unpacking setuptools
Installing collected packages: pip, setuptools
Successfully installed pip setuptools
Cleaning up...
Após a instalação o pip deve ser adicionado ao path. Para isso deve ser aberto o explorer e utilizado o botão direito do mouse para clicar em computador. Depois deve ser acessado o menu propriedades, Configurações Avançadas de Sistema, e na porção Variáveis do Sistema a variável Path deve ser editada para conter em seu final o diretório de instalação do pip. No caso da instalação padrão, esse endereço é C:\Python27\Scripts. A figura 3.01 exemplifica o processo:

Figura 3.01: Adicionado pip ao path
|
O instalador atual do Python 2.7.9 para Windows já vem com pip instalado por padrão. Além disso, no processo de instalação é possível marcar opção para adição automática do Python ao path, evitando todo o trabalho manual. |
Com o pip instalado deve ser executado o comando pip install virtualenv para instalar o virtualenv.
Finalmente com o virtualenv instalado e o template de projeto extraído na “Área de Trabalho” é possível criar o ambiente isolado para o projeto através da linha de comando.
Para rodar o comando é necessário abrir o prompt como administrador.
Para isso, clique no menu Iniciar, digite cmd. Mantenha pressionadas as teclas Crtl + Shift e então pressione enter:
Criação de virtualenv
C:\Users>cd renzovm
C:\Users\renzovm>cd Desktop
C:\Users\renzovm\Desktop>cd appengineepython
C:\Users\renzovm\Desktop\appengineepython>cd backend
C:\Users\renzovm\Desktop\appengineepython\backend>cd venv
C:\Users\renzovm\Desktop\appengineepython\backend\venv>venv.bat
Ao término da execução, o ambiente virtual será criado e as bibliotecas necessárias instaladas.
Arquivo requirements.txt
As dependências de bibliotecas externas do projeto se encontram em dois arquivos: requirements.txt e dev_requirements.txt,
presentes no diretório venv. No primeiro se encontram as dependências necessárias ao funcionamento do servidor:
Arquivo requirements.txt
tekton==4.0
gaebusiness==4.4.2
gaecookie==0.7
gaeforms==0.5
gaegraph==3.0.2
gaepermission==0.8
pytz==2014.4
Babel==1.3
python-slugify==0.0.7
Já o arquivo dev_requirements contém dependências necessárias apenas durante processo de desenvolvimento.
Esse é o caso das bibliotecas Mock e Moomygae, utilizadas para facilitar a criação de testes automáticos:
Arquivo dev_requirements.txt
-r requirements.txt
mock==1.0.1
mommygae==1.1
Esses arquivos podem ser editados para conter dependências que se julguem necessárias durante o desenvolvimento.
Links Simbólicos
Em um servidor comum o processo de instalação de dependências seria feito com os mesmos comandos.
Contudo, não temos acesso ao Sistema Operacional do GAE.
Sendo assim, é necessário informar ao sistema onde se encontram as bibliotecas, de forma que elas sejam copiadas durante o processo de deploy.
Além das bibliotecas, também devem ser dispobilizados os códigos das aplicações presentes no diretórios apps.
Essas tarefas são realizadas pelo script de setup.
Ele cria os links simbólicos apps e lib na pasta appengine apontado para os diretórios de interesse.
Virtualenv e Pycharm
As ações realizadas até agora serviram apenas para instalar as bibliotecas no projeto. Contudo, o Pycharm precisa utilizar o ambiente criado para poder auxiliar no desenvolvimento, oferecendo opções de auto complete referente às bibliotecas e apps.
Para isso deve ser acessada a janela de configuração da IDE (ícone chave de roda).
No input de pesquisa deve ser inserida a palavra Interpreter e escolhida a opção Python Interpreters. Deve ser pressionado o ícone “+” e escolhida a opção Local..., conforme figura 3.02:

Figura 3.02: Virtualenv e Pycharm
Feito isso, deve ser selecionado o ambiente isolado localizado dentro do projeto.
No Linux o arquivo a ser escolhido é /backend/venv/bin/python. Já no Windows ele se encontra em\backends\venv\Scripts\python.exe.
Indicado o virtualenv, deve ser editada a localização do servidor. Para isso deve ser acessado o menu “Edit Configurations…” conforme figura 3.03:

Figura 3.03: Configuração de servidor no Pycharm
Uma vez nessa janela, deve ser configurado o diretório appengine do projeto como “Working directory”,
já que ele contém o arquivo app.yaml. A figura 3.04 mostra a configuração final:

Figura 3.04: Diretório do servidor
Após toda essa configuração é possível executar o servidor local e verificar a página inicial do projeto no navegador, conforme figura 3.05:

Figura 3.05: Hello World Tekton
Como último passo de configuração, é necessário então marcar pastas chave como raízes de código fonte. Isso é necessário para que a IDE consiga inspecionar seus conteúdos a fim de auxiliar no processo de desenvolvimento. A figura 3.06 mostra o menu a ser acessado quando se clica com botão direito do mouse sobre cada um dos diretórios:

Figura 3.06: Raiz de código fonte
Os diretórios a serem marcados são:
- apps;
- test;
- appengine.
Apesar de trabalhoso, a parte de configuração do projeto é feita somente uma vez. Com os ganhos de produtividade, o tempo gasto na configuração será recuperado rapidamente durante a fase desenvolvimento.
Script convention.py
No projeto configurado o framework Tekton utiliza os objetos básicos do Webapp2. Sendo assim, é possível investigar o arquivo de configuração app.yaml para entender seu funcionamento:
Seção handlers do arquivo app.yaml com Tekton configurado
handlers:
- url: /
*script*: convention.app
secure: always
- url: /robots\.txt
static_files: static/robots.txt
upload: static/robots.txt
- url: /favicon\.ico
static_files: static/img/favicon.ico
upload: static/img/favicon.ico
- url: /static(.*)
static_files: static\1
upload: static.*
- url: /[^_].*
*script*: convention.app
secure: always
- url: /_ah/warmup
*script*: routes.warmup.app
Da configuração é possível notar que as requisições serão tratadas, com apenas algumas exceções, pelo arquivo convention.py.
O conteúdo desse script se encontra no código 3.01.
Nas linhas 2 e 3 do arquivo são adicionados os diretórios lib e apps, presentes em appengine, ao path.
Essa é a razão de se ter criado os links simbólicos nesse endereço, na seção de Setup Inicial.
Nas linhas 11 e 12 são definidos os parâmetros de localização e fuso horário da aplicação. Mais detalhes sobre isso será visto na seção “Arquivo settings.py”.
Nas demais linhas é definido um único RequestHandler que trata todas requisições.
É importante ressaltar que tanto chamadas POST e GET são tratadas pelo mesmo método make_convention.
Se for necessário atender outros métodos, como o PUT, é suficiente editar o arquivo copiando o método get e substituindo seu nome para também atender a respectiva chamada HTTP.
Código 3.01: Script convention.py
1 # Put lib on path, once Google App Engine does not allow doing it directly
2 sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))
3 sys.path.append(os.path.join(os.path.dirname(__file__), "apps"))
4
5 import settings
6 from tekton.gae import middleware
7 import webapp2
8 from webapp2_extras import i18n
9
10 i18n.default_config['default_locale'] = settings.DEFAULT_LOCALE
11 i18n.default_config['default_timezone'] = settings.DEFAULT_TIMEZONE
12
13
14 class BaseHandler(webapp2.RequestHandler):
15 def get(self):
16 self.make_convention()
17
18 def post(self):
19 self.make_convention()
20
21 def make_convention(self):
22 middleware.execute(settings.MIDDLEWARE_LIST, self)
23
24
25 app = webapp2.WSGIApplication([("/.*", BaseHandler)], debug=False)
A ideia geral é a delegação do processamento de todas requisições pelo script convention.py,
não mais sendo necessário mapear handlers manualmente. A maneira de se fazer isso será explicada na próxima seção.
Roteamento via Tekton
O roteamento no framework Tekton é feito por convenção entre o conteúdo do pacote routes,
presente no diretório appengine, e os paths da aplicação.
Como exemplo inicial, ao se fazer o acesso à raiz “/” do projeto, a biblioteca irá procurar pelo módulo home.py,
dentro do diretório routes, e executar sua função de nome index. O código 3.02 apresenta o código do arquivo:
Código 3.02: home.py
1 @login_not_required
2 @no_csrf
3 def index():
4 return TemplateResponse()
O resultado da execução do código foi visto no final da seção de Setup, na figura 3.05, onde aparecia a home do Tekton.
No caso do acesso ao path /usuario, a biblioteca irá procurar pelo script usuario.py e executar sua função index.
Já o acesso a /usuario/ola acarretará na execução da função ola do script usuario.py.
O arquivo se encontra sob o pacote routes:
routes/
├── home.py
├── usuario.py
O código 3.03 apresenta o conteúdo do script:
Código 3.03: script usuario.py
1 @login_not_required
2 @no_csrf
3 def index(_resp):
4 _resp.write('Página de Usuário')
5
6
7 @login_not_required
8 @no_csrf
9 def ola(_resp):
10 _resp.write('Olá Usuário')
Ao acessar o link http://localhost:8080/usuario é possível visualizar a mensagem “Página de Usuário” no navegador. De maneira análoga, ao se acessar http://localhost:8080/usuario/ola será visualizada a mensagem “Olá Usuário”.
Seguindo esse esquema de convenção, podem ser criados pacotes, módulos e funções dentro de routes.
Os scripts serão sempre encontrados de acordo com o path acessado no navegador.
Dessa maneira fica dispensada a configuração do roteamento que foi necessária no capítulo 2: Webapp2.
Recebimento de Parâmetros
Além da convenção de rotas, o recebimento de parâmetros também é feito por convenção. Para receber o valor de um parâmetro chamado “nome” é necessário apenas declarar um argumento de mesmo nome. Como exemplo, a função ola foi alterada conforme código 3.04:
Código 3.04: função ola com parâmetro nome
1 def ola(_resp, nome):
2 _resp.write("Olá %s" % nome)
A figura 3.07 mostra o resultado da passagem de parâmetro via query string:

Figura 3.07: execução de ola com parâmetro “nome” igual a “Renzo”
Mais parâmetros podem ser recebidos acrescentando-se argumentos à função, conforme código 3.05:
Código 3.09: função ola com parâmetros nome e sobrenome
1 def ola(_resp, nome, sobrenome):
2 _resp.write("Olá %s %s" % (nome, sobrenome))
Acessando o endereço http://localhost:8080/usuario/ola?nome=Renzo&sobrenome=Nuccitelli é exibida no navegador a mensagem “Olá Renzo Nuccitelli”.
Cabe ressaltar que a passagem de parâmetros pode ser feita de maneira RESTful. Ou seja, a mesma mensagem é obtida se os parâmetros forem passados através do endereço:
http://localhost:8080/usuario/ola/Renzo/Nuccitelli.
É possível ainda mesclar as duas formas. Como exemplo, o acesso ao endereço:
http://localhost:8080/usuario/ola/Renzo?sobrenome=Nuccitelli
A mensagem seria exatamente a mesma vista anteriormente.
Configurações Globais e Internacionalização
O arquivo settings.py contém as configurações globais do sistema.
Nele é possivel alterar endereço responsável por envio de emails pelo sistema, idioma, fuso-horário, entre outras.
Como exemplo, o arquivo foi editado para português brasileiro e fuso de São Paulo:
Arquvio setting.py
1 APP_URL = 'https://tekton-fullstack.appspot.com'
2 SENDER_EMAIL = 'renzon@gmail.com'
3 DEFAULT_LOCALE = 'pt_BR'
4 DEFAULT_TIMEZONE = 'America/Sao_Paulo'
5 LOCALES = ['en_US', 'pt_BR']
6 TEMPLATE_404_ERROR = 'base/404.html'
7 TEMPLATE_400_ERROR = 'base/400.html'
Após alterar o idioma é necessário rodar o script para gerar o arquivo de traduções.
O Tekton já possui embutida biblioteca de internacionalização, facilitando a construção de sites em múltiplas línguas.
Maiores detalhes serão vistos em capítulos posteriores. Por ora, é suficiente rodar o script build_*script*s/babel/i18n_extractor.py.
Gerados os arquivos, é possível verificar a home page traduzida:

Figura 3.08: Home em português brasileiro
Injeção de Dependência
O framework Tekton provê um sistema simplificado de injeção de dependência por convenção de nomes.
Ao declarar parâmetros com identificação especial, a biblioteca injeta objetos de interesse automaticamente.
É o caso do parâmetro _resp constante no código 3.09, reproduzido abaixo por comodidade.
Através dele se tem acesso ao objeto Response, visto em detalhes no capítulo 2: Webapp2.
Código 3.09: função ola com parâmetros nome e sobrenome
1 def ola(_resp, nome, sobrenome):
2 _resp.write("Olá %s %s" % (nome, sobrenome))
Se fosse necessário receber também o objeto Request como dependência,
seria suficiente acrescentar um segundo parâmetro à função, conforme código código 3.10:
Código 3.10: função ola com injeção de objetos Response e Request
1 def ola(_resp, _req, nome, sobrenome):
2 _resp.write("Olá %s %s" % (nome, sobrenome))
3 # Imprimindo parametros de requisição http
4 _resp.write("Parametros: %s" % _req.arguments())
|
O underscore “_” na frente dos parâmetros injetados é apenas uma convenção.
Ela foi adotada para diferenciar os argumentos que são injetados daqueles que são extraídos da requisição HTTP.
Dessa maneira é possível perceber no código 3.10 que |
|
Os parâmetros injetados devem sempre ser os primeiros argumentos da função. Sendo assim, não seria possível trocar de posição os parâmetros |
Da mesma forma que foram extraídas as dependências do framework Webapp2, a mesma ideia pode ser empregada para outros objetos ou funções. A vantagem dessa técnica é tornar o código testável. Isso ficará mais claro no capítulo de Testes Automatizados.
Redirecionamento
Uma vez que o objeto RequestHandler é recebido como injeção de dependência,
para fazer um redirecionamento é necessário apenas recebê-lo através do parâmetro _handler e utilizar seu método redirect.
O código 3.11 mostra o método redirecionar que redireciona para o path respectivo à função ola:
Código 3.11: Método Redirecionar com url como string, script usuario.py
1 @login_not_required
2 @no_csrf
3 def redirecionar(_handler):
4 url = r'/usuario/ola/Renzo/Nuccitelli'
5 _handler.redirect(url)
O problema dessa abordagem é que o path é inserido como uma string.
Se por alguma razão o nome da função ola for alterado, o redirecionamento levará a um link quebrado.
Por essa razão o Tekton provê uma interface para calcular a url a partir de uma função.
A código 3.12 mostra o código alterado, fazendo uso do módulo router,
presente no pacote tekton, para gerar a url baseada na função, que é seu primeiro parâmetro.
Nessa abordagem, em caso de refatoração da função ola, o link do redirecionamento iria ser atualizado automaticamente para o endereço correto.
Código 3.12: Método Redirecionar com url calculada por tekton.router.py
1 def redirecionar(_handler):
2 url = r'/usuario/ola/Renzo/Nuccitelli'
3 url = router.to_path(ola, 'Renzo', 'Nuccitelli')
4 _handler.redirect(url)
Dessa maneira se encerra a explicação das funcionalidades básicas da biblioteca.
Resumo
Nesse capítulo foi abordado o framework Tekton. Foi utilizado o virtualenv para sua instalação e o arquivo requirements.txt para resolução de dependências de outras bibliotecas.
Foram explicadas as várias convenções, visando evitar excessiva configuração:
- Roteamento via localização de módulos sob o pacote
routes; - Recebimento de parâmetro por convenção de nomes;
- Injeção de Dependências permitindo acesso a objetos do Weapp2;
- Utilização de configurações globais.
Por fim, foi visto como se utilizar o módulo tekton.router.py para calcular paths,
permitindo a atualização automática de endereços no caso de refatoração de nomes de funções.
Com base nesse conhecimento serão construídos todos os exemplos constantes no restante desse livro.
Questões
- Para que serve o Virtualenv?
- Qual a função do arquivo
convention.py? - Por que é necessário incluir bibliotecas através de código no arquivo
convention.py? - Como ficaria a declaração de uma função para tratar a execução de chamada no path
/usuario/salvar?nome=Renzo&idade=31? - Como se diferenciam parâmetros recebidos por injeção de dependência dos recebidos via requisição HTTP?
- Qual deve ser a posição de parâmetros recebidos via injeção de dependência?
- Qual deve ser o parâmetro declarado quando for necessário fazer um redirecionamento?
- Qual o script e função devem ser utilizados para se calcular paths com base em uma função?
Respostas
- O Virtualenv serve para se criar ambientes Python isolados para cada projeto.
- O arquivo
convention.pyserve para fazer a ligação entre o Tekton e o Wepapp2. Ele é o arquivo onde se encontra o handler que delega todas requisições para funções que se encontram sob o pacoteroutes, através de convenção. - É necessário incluir bibliotecas através de código no arquivo
convention.pyporque diferente de servidores tradicionais, na plataforma GAE não se tem acesso ao Sistema Operacional para se poder instalar as bibliotecas utilizando virtualenv e pip. - A declaração da função seria
def salvar(nome,idade)e deveria constar no scriptusuario.py. - Parâmetros recebidos via injeção de dependência possuem a convenção de conter o prefixo “_” antes de seus nomes. Parâmetros recebidos via requisição HTTP são escritos sem esse prefixo.
- Parâmetros recebidos via injeção de dependência devem sempre ser os primeiros a serem declarados em uma função.
- Quando for necessário fazer um redirecionamento deve ser declarada a dependência
_handlerpara acessar a instância deRequestHandlerque está tratando a requisição. Deve ser utilizado o métodoredirectdesse objeto para se efetuar o redirecionamento. - Deve ser utilizada a função
to_pathdo módulotekton.routerpara se calcular o path respectivo a uma função, que deve ser passada como parâmetro.
- “You must renounce all superficiality, all convention, all vanity and delusion.”↩