REST em Python com Bottle – parte 2

rest-2_abertura

Na primeira parte, a REST foi deixada funcional porém sem a opção responsável pela atualização da base de dados. Agora, além da implementação desta, há também (meio que como um bônus) algumas poucas palavras sobre o funcionamento do front-end desenvolvido¹ para justamente testar a API — até tinha pensado em exemplos que utilizassem o curl mas no final acabei optando por uma abordagem mais prática.

(¹) Desenvolvido nada, tinha uma grande parte dele já pronta… 🙂

Antes de começar

Diferente do que fiz em outras vezes onde fui  colocando o novo código dentro de um novo branch preferi atualizar diretamente o repositório e usar o recurso de tags do Git para criar as versões parte-1 e parte-2 do programa. As tags (etiquetas) servem para marcar um determinado estado no tempo da MASTER. É como se fosse um “instantâneo” daquele momento e deixasse uma referência daquele ponto para voltar a ele sempre que necessário.

Este recurso é usado para produzir as “versões” que ferramentas como BitBucket, GitHub , GitLab etc… utilizam para produzir a opção de download do programa (código fonte e/ou binários) sem a necessidade de se clonar o repositório.

Assim basta executar git pull para atualizar seu repositório local.

Sobre o front-end

rest-2_front-end

Antes de voltar ao à REST algumas palavras sobre o front-end. Este foi desenvolvido inteiramente com HTML e utilizando Bootstrap, JQuery e os recursos de templates do Bottle para simplificar a montagem das partes como cabeçalho do HTML, título da página, rodapé e a montagem do formulário.

Formulário

Especificamente na parte do formulário, algo que detesto fazer, ele é montado a partir de um arquivo JSON — o “./static/json/users.json” — que descreve as características dos campos que ele conterá, algo assim:

...
{
    "type": "str",
    "field": "name",
    "label": "Nome",
    "size": 80,
    "required": true,
    "tip": "digite seu nome"
},
...

No arquivo “./template/user_template.tpl” está a inteligência necessária para transformá-lo em um formulário HTML de verdade contendo todos os elementos necessários e também as classes do Bootstrap.

Ou seja, a automatização aqui envolve apenas a confecção da casca daquilo que é enviado ao usuário (inclusive o HTML e o REST estão em “lugares” diferentes do servidor) por mais que foi tentador simplificar algumas coisas diretamente em “./template/edit.tpl”. 🙂

Javascript

O preenchimento do conteúdo, validação e envio para o servidor são realizados diretamente no navegador web do cliente utilizando rotinas em Javascript e com uma “ajudinha” do JQuery. Todos estão dentro do diretório “./static”:

  • edit.js — Recupera o conteúdo do registro via GET, preenche o formulário com a informação carregada, cuida da validação do CPF (feita fora do HTML5) e faz o envio dos novos valores via PUT.
  • new.js — É bem parecido com o edit.js, também cuida da validação do CPF e de enviar os dados inseridos do novo usuário via POST.
  • show.js — Recupera todos os registros inseridos via GET e insere as linhas da tabela diretamente no DOM e com as respectivas opções para alteração e remoção dos registros. Aqui também está a rotina que apaga um registro via DELETE.
  • users.js — Basicamente são rotinas utilizadas tanto por edit.js quanto new.js.

Todas as rotinas estão razoavelmente comentadas mas se houver interesse posso explicá-las com um pouco mais detalhes.

De volta à REST

Agora é possível voltar à implementação daquilo que ficou faltando na REST.

Recuperação dos dados — retrieve

De novo? Este cara já não estava na primeira parte? Sim e não, o método implementado na primeira parte retorna toda a base de dados e não um único registro. A abordagem da primeira para é útil quando se quer visualizar todos os dados de uma só vez, seja com a rotina que está em “show.js” — ou com ferramentas externas como é o caso da DataTables.

Mas também é importante que existe uma opção que permita recuperar os dados de um único registro, neste caso para permitir sua sua edição, duplicação do conteúdo etc.

...
@get('/users/<user:int>')
def retrieve(user):
    '''trata a recuperação da informação de um único usuário'''
    try:
        # consulta a base de dados
        if user>0:
            cursor.execute('''SELECT id,name,address,cpf,phone,email,'''+\
            '''sites FROM users WHERE id=? LIMIT 1''',(str(user)))
            connection.commit()
            row=cursor.fetchone()
            if not row:
                # se o registro não existe, sinaliza erro
                raise KeyError
            else:
                json_output=[{ 'id':row[0], 'name':row[1], 'cpf':row[2], \
                'address':row[3], 'phone':row[4], 'email':row[5], 'sites':row[6]}]
        else:
            raise KeyError
    except KeyError:
        response.status=404
        return

    response.headers['Content-Type'] = 'application/json'
    response.headers['Cache-Control'] = 'no-cache'
    return json.dumps(json_output)
...

Ele é bem parecido com o método anterior, o que retorna todos, mas como será alimentado com informações vindas do “lado de fora” há alguma crítica extra. A primeira está na descrição do próprio método definido que o valor de  “<user:int>” força o uso de um inteiro — fiz o mesmo no método de remoção mas acabei esquecendo de comentar.

Qualquer erro aqui retornará com código 404.

Atualizando um registro — update

A atualização é feita através do método PUT do HTTP e como o envio de um JSON contendo os novos valores para o registro.

...
@put('/users/<user:int>')
def update(user):
    '''trata a atualização dos registros'''
    try:
        try:
            # recupera os dados enviados em JSON
            data = request.json
        except:
            # não há JSON, sinaliza erro
            raise ValueError

        if data is None:
            # o JSON está vazio, sinaliza erro
            raise ValueError

        try:
            # recupera as informações e ao mesmo tempo valida as chaves do JSON
            # o ID do usuário fica no final da tupla
            record = (data['name'],data['cpf'],data['address'],data['phone'],\
            data['email'],data['sites'],str(user))
        except:
            # faltam chaves no JSON, sinaliza erro
            raise ValueError
    except ValueError:
        # algo diferente ao esperado foi recebido, retorna '400 Bad Request'
        response.status=400
        return

    try:
        # atualiza o registro no banco de dados
        cursor.execute('''UPDATE users SET name=?,cpf=?,address=?,phone=?,'''+\
        '''email=?,sites=? WHERE id=?''',record)
        connection.commit()
    except:
        # algo aconteceu, retorna com '500 Internal Server Error'
        response.status=500
        return

    # retorna '200 success' confirmando a alteração
    response.status=200
    return
...

Ele tem estrutura parecida com o método de inclusão, inclusive nos códigos de retornos dos erros. As diferenças estão no fato do comando em SQL que e utilizado, “UPDATE” ao invés de “INSERT”, e da necessidade de receber o ID do registro que será atualizado.

Considerações finais

Fiz algumas concessões para não deixá-lo extenso como optar por não revalidar os dados recebidos pela REST, não tratar todos os possíveis erros e ignorar a autenticação para quem está se conectando (algo importante “no mundo real”).

O mesmo também pode ser dito do Javascript onde fui otimista no tratamento dos códigos de retorno da REST mas vale a pena dar uma olhada na rotina de remoção de registros em “show.js” para entender como fazê-lo.

E como o programa está disponível no GitHub, aguardo os pull requests! 😀

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s