REST em Python com Bottle – parte 1

rest-1_abertura

Algo que acabei não gostando quando da conclusão do LED Blink foi justamente da implementação da REST ter ficado incompleta — mesmo que a ideia nunca fosse implementá-la por completo por não haver necessidade. Então resolvi fazer uma implementação completa, dentro de uma nova aplicação (um cadastro “bem básico” de usuários) aproveitando a mesma infraestrutura já usada com o LED Blink, ou seja, Pyton com Bottle, SQLite no lado do servidor (o back-end) e Bootstrap e JQuery na lado do cliente (o front-end).

Esta primeira parte contempla quase toda a implementação da REST exceto pela atualização (“update”) que preferi deixar para a segunda parte junto com o o HTML (ficará bem mais fácil).

O que é REST?

A REST (ou RESTful como também é conhecida) é o acrônimo para Representational state transfer — ou transferência de estado representacional — e, pegando emprestada a curtíssima definição que usei na parte 3 d’Aplicação web em Python e Bottle é:

(…) a implementação de um CRUD é feita a partir de uma abstração que utiliza os comandos do próprio protocolo HTTP. Assim o comando POST é usado para criar um novo objeto, o GET para recuperar um ou mais objetos, o PUT para atualizar um objeto já existente e o DELETE para removê-lo.

Aliás, não sei como ainda não apanhei por dizer algo assim mas do ponto de vista de implementação é (quase que) justamente isto. 🙂

O programa

Basta fazer um clone local do repositório com:

$ git clone https://github.com/plainspooky/rest-em-python-com-bottle.git
Cloning into 'rest-em-python-com-bottle'...
...
Checking connectivity... done.

Daí é inicializar o ambiente do Virtualenv e instalar as dependências do programa:

$ cd rest-em-python
$ virtualenv --python $( which python3 ) py3
$ source ./py3/bin/activate
(py3) $ pip install -r requirements.txt

Crie a base de dados usando o SQLite¹:

(py3) $ sqlite3 ./db/users.db < ./db/users.sql

Execute o servidor:

(py3) $ ./server.py

E pronto!

(¹) Talvez possa ser necessário instalá-lo.

A tal da REST…

A REST está implementada no arquivo “./api/users.py” está baseada nos exemplos do Building a Rest API with the Bottle Framework e é responsável de receber, interpretar e responder as requisições em HTTP e fazer interface com a base de dados.

Inserção de um registro — create

A inserção de novos registros é feita usando o comando POST do HTTP com os dados a serem armazenados (o registro) codificados dentro de um JSON dentro dele. Diferente do LED Blink que só ligava/desligava um LED a abordagem aqui precisa ser bem menos otimista, assim há um conjunto de try … except … para “proteger” tanto aplicação como base de dados.

...
@post('/users')
def creation():
    ''' insere um novo registro '''
    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
            record = (data['name'],data['cpf'],data['address'],data['phone'],\
            data['email'],data['sites'])
        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
   ... 

O primeiro bloco de try … except … está relacionado com os dados que chegam da página web e:

  1. Verifica se os dados foram mandados em JSON;
  2. Se o JSON possui algo dentro dele e
  3. Se todas as campos (chaves) possuem conteúdo.

Se algum destes quesitos não forem satisfeitos uma exceção de ValueError é produzida fazendo com que o programa retorne com código 400 (“Bad Request”).

    ...
    try:
        # insere o registro no banco de dados de acordo com as regras que a
        # mãe do Robert'); DROP TABLE Students; -- espera que as pessoas usem.
        # ( consulte http://bobby-tables.com/python )
        cursor.execute('''INSERT INTO users(name,address,cpf,phone,email,sites)'''+\
        '''VALUES (?,?,?,?,?,?)''',record)
        connection.commit()
        # recupera o 'id' do registro que foi inserido
        cursor.execute('''SELECT last_insert_rowid() LIMIT 1''')
        last_id=cursor.fetchone()
    except:
        # algo aconteceu, retorna com '500 Internal Server Error'
        response.status=500
        return

    # retorna '200 success' com o ID do registro inserido
    response.status=200
    response.headers['Content-Type'] = 'application/json'
    return json.dumps({ 'id': last_id[0] })
...

O segundo bloco de try … except … cuida da inserção do registro na base de dados de acordo com aquilo que a mãe do pequeno Robert’); DROP TABLE Students; – – espera que seja feito. 🙂

https://xkcd.com/327/

Aqui retorna um código 500 (“Server Error”) caso algo errado aconteça e dando tudo der certo o registro é salvo e o código 200 (“Success”) retornado.

Recuperação dos dados — retrieve

A recuperação é feita através do comando GET do HTTP e neste caso todo o conteúdo da tabela “users” é enviada formatada em JSON.

...
@get('/users')
def retrieve():
    '''trata a recuperação a informação dos usuários'''
    # consulta a base de dados
    cursor.execute('''SELECT id,name,address,cpf,phone,email,sites FROM users ORDER BY id''')
    json_output=[]
    for row in cursor.fetchall():
        json_output.append({ 'id':row[0], 'name':row[1], 'cpf':row[2], 'address':row[3],\
        'phone':row[4], 'email':row[5], 'sites':row[6]})
    # retorna o JSON contendo todos os itens
    response.headers['Content-Type'] = 'application/json'
    response.headers['Cache-Control'] = 'no-cache'
    return json.dumps(json_output)
...

Este é o método mais simples e o que ele faz é consultar a base de dados, popular um dicionário com os registros lidos e finalmente enviá-los formatados em um JSON.

Remoção de um registro — delete

Um registro é apagado através do comando DELETE do HTTP, acompanhado o comando vai o respectivo ‘id’ do registro que se deseja remover — aqui não existe confirmação da ação, esta parte fica com a interface.

...
@delete('/users/<user:int>')
def delete(user):
    '''trata a remoção de registros'''
    try:
        # consulta a base de dados antes de remover
        cursor.execute('''SELECT id FROM users WHERE id=? LIMIT 1''',(user))
        connection.commit()
        if len(cursor.fetchall())==0:
            # se o registro não existe, sinaliza erro
            raise KeyError
        else:
            # senão o remove do banco de dados
            cursor.execute('''DELETE FROM users WHERE id=? LIMIT 1''',(str(user)))
            connection.commit()

    except KeyError:
        response.status=404
        return

    return
...

O que este método faz é consultar o ‘id’ informado, caso ele exista o apaga e caso contrário o código 404 (“Not found”) é retornado.

Fim por enquanto…

Por enquanto é só, na próxima parte será a vez da atualização dos dados de um registro e também das entranhas do front-end, a parte em HTML, Javascript etc que está cuidando da interface com o usuário. Aliás a opção de inclusão de usuários faz uma série de validações no conteúdo dos campos antes do envio para o servidor.

Até!

Anúncios

Um comentário sobre “REST em Python com Bottle – parte 1

  1. Pingback: REST em Python com Bottle – parte 2 | giovannireisnunes

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