Construir APIs REST em Django nos dias atuais é uma tarefa no máximo corriqueira. Com a ajuda de bibliotecas consagradas, como Django REST Framework e Tastypie, e utilizando toda a abstração que envolve os Models do framework, é possível ter endpoints respondendo em questão de minutos.
Mas existem alternativas que são capazes de tornar esse processo ainda mais prático, e com uma curva de aprendizado um pouquinho menor. Nesse seleto grupo, uma biblioteca que me agrada é o Restless.
Daniel Lindsley (também conhecido como Toast Driven), um dos criadores do Tastypie (famosa biblioteca Django para construção de APIs RESTful), também é criador do Restless. Segundo ele, o que o motivou a escrever uma alternativa ao Tastypie foi:
Quite simply, I care about creating flexible & RESTFul APIs. In building Tastypie, I tried to create something extremely complete & comprehensive. The result was writing a lot of hook methods (for easy extensibility) & a lot of (perceived) bloat, as I tried to accommodate for everything people might want/need in a flexible/overridable manner.
Não há dúvidas que as duas bibliotecas citadas no início desse artigo são extremamente completas. Se você estiver procurando um engine poderoso para a criação de APIs em Django, pule logo para a documentação do Django REST Framework.
Mas nem sempre precisamos de todo esse poderio para dar vida ao nosso projeto. E é exatamente com esse posicionamento que Daniel fecha o seu argumento:
But in reality, all I really ever personally want are the RESTful verbs, JSON serialization & the ability of override behavior.
Se assim como o autor você só precisa dos verbos HTTP, serialização JSON e uma leve camada de customização, talvez o Restless seja tudo o que você necessita.
Por último mas não menos importante: O Restless, é "framework agnostic" e funciona com Django, Flask, Pyramid e Tornado.
Lembra do nosso "mini IMDB", lá do post sobre REST? Vamos aproveitar a temática para exemplificar
o uso de Restless com Django. Mas antes, aquele pip
"de leve" para instalar o pacote:
$ pip install restless
Não é necessária a adição da biblioteca ao settings.py
.
Relembrando o exemplo do post anterior, precisaremos construir os seguintes recursos:
GET /movies
: Trará uma lista de filmesPOST /movies
: Adicionará um novo filme ao banco de dadosGET /movies/{id}
: Trará um filme específicoPUT /movies/{id}
: Atualizará um filme específicoDELETE /movies/{id}
: Removerá um filme específico do banco de dadosPara tanto, teremos um model Django similar ao abaixo:
# movies/models.py
import uuid
from django.db import models
class Movie(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
Podemos partir agora para a criação dos resources.
Resources são classes que descrevem um recurso REST, dentro do Restless. É através deles que definiremos endpoints e todos os verbos suportados. Já os Preparers fazem o papel do "mapping" do objeto que você está expondo na API.
Para começar, vamos criar um arquivo api.py
dentro do nosso app:
# movies/api.py
from restless.dj import DjangoResource
from restless.preparers import FieldsPreparer
from movies.models import Movie
class MovieResource(DjangoResource):
preparer = FieldsPreparer(fields={
'id': 'id',
'title': 'title',
'description': 'description',
})
def list(self):
return Movie.objects.all()
O DjangoResource
é um Resource Restless com algumas "facilidades" para funcionar dentro do
framework Django. Uma dessas facilidades é o roteamento (que veremos logo abaixo).
Todo recurso precisa de um atributo preparer
, é através dele que o Resource fará o "de-para"
dos campos no seu banco dados, e os campos que serão exibidos em sua API. Nesse caso
estamos utilizando o FieldsPreparer
, e através de um dicionário (onde a chave é o nome do campo
na API e o valor é o caminho para o dado) fazemos o mapeamento necessário para expor os
dados do recurso.
O método list
será o responsável por responder uma rota GET /movies/
em nosso projeto. Aqui já
podemos ver o conceito de BYOD (Bring Your Own Data) em ação. O Restless não "introspecta"
os seus models Django para entender o que e como ele deve expor dados. É necessário que
você faça isso explicitamente... é esse princípio que torna fácil o trabalho da biblioteca com outros
frameworks e bancos de dados.
Falta atualizar o urls.py
para que o Django possa fazer o roteamento dos endereços
com as views:
# urls.py
from django.conf.urls import url, include
from movies.api import MovieResource
urlpatterns = [
url(r'movies/', include(MovieResource.urls())),
]
Aquele .urls()
é uma das facilidades que o DjangoResource
oferece. Caso você esteja trabalhando
com algum outro framework, você pode estender o comportamento de Resource
e resolver o roteamento
manualmente.
Pronto! O endpoint movies/
já está respondendo:
$ curl -X GET http://localhost:8000/movies/ | json_pp
{
"objects" : [
{
"description" : "The Rebel Alliance makes a risky move to steal the plans for the Death Star, setting up the epic saga to follow. (133 mins.)",
"id" : "7e383f9c-5dc4-4b0d-9864-71e05a5d271b",
"title" : "Rogue One"
},
(...)
]
}
Para adicionar um filme, faremos um POST em movies/
, para isso, precisamos adicionar um método create
:
# movies/api.py
(...)
class MovieResource(DjangoResource):
(...)
def list(self):
return Movie.objects.all()
def create(self):
return Movie.objects.create(
title=self.data['title'],
description=self.data['description'],
)
Mais uma vez o Restless não introspecta o modelo, logo, é necessário fazer a validação e criação do objeto em banco "na mão".
Vamos testar se o comportamento é o esperado:
$ curl -X POST -H "Content-Type: application/json" -d '{"title": "just a test", "description": "foo bar"}' http://localhost:8000/movies/
Provavelmente retornará um exception dizendo que o acesso não foi autorizado. O Restless por padrão espera
que todo o POST
, PUT
e DELETE
seja realizado através de uma requisição autenticada. Para isso, antes
de executar cada verbo, ele consulta o método is_authenticated
.
Para fins educacionais, vamos informar à biblioteca que todas as requisições são aceitas:
# movies/api.py
(...)
class MovieResource(DjangoResource):
(...)
def create(self):
return Movie.objects.create(
title=self.data['title'],
description=self.data['description'],
)
def is_authenticated(self):
return True
Mais uma vez executamos o POST via curl
, e voilà:
{
"title": "just a test",
"id": "bd02d0eb-4b75-4bfe-8222-ba24f7a1cad4",
"description": "foo bar"
}
Já listamos e criamos novos filmes. Como fazemos para ver os detalhes de um filme através do
seu ID? Simples! O método detail
resolve essa para a gente:
# movies/api.py
from django.shortcuts import get_object_or_404
(...)
class MovieResource(DjangoResource):
(...)
def detail(self, pk):
return get_object_or_404(Movie, pk=pk)
Um GET
para movies/{id}
retornará os detalhes do recurso:
$ curl -X GET http://localhost:8000/movies/7e383f9c-5dc4-4b0d-9864-71e05a5d271b/
{
"description": "The Rebel Alliance makes a risky move to steal the plans for the Death Star, setting up the epic saga to follow. (133 mins.)",
"id": "7e383f9c-5dc4-4b0d-9864-71e05a5d271b",
"title": "Rogue One"
}
Note que o DjangoResource
resolveu a passagem do parâmetro pk
.
Para finalizar, vamos criar os métodos update
e delete
, que serão responsáveis
por atualizar e remover o recurso:
# movies/api.py
(...)
class MovieResource(DjangoResource):
(...)
def detail(self, pk):
return get_object_or_404(Movie, pk=pk)
def update(self, pk):
movie = self.detail(pk)
movie.title = self.data['title']
movie.description = self.data['description']
movie.save()
return movie
def delete(self, pk):
movie = self.detail(pk)
movie.delete()
Assim como o método create
, não há nenhuma "mágica" resolvendo a operação para a gente.
Precisamos explicitamente realizar a atualização e remoção do dado através do ORM do Django.
Agora somos capazes de atualizar:
$ curl -X PUT -H "Content-Type: application/json" -d '{"title": "Zombieland", "description": "So much fun"}' http://localhost:8000/movies/bd02d0eb-4b75-4bfe-8222-ba24f7a1cad4/
{
"title": "Zombieland",
"description": "So much fun",
"id": "bd02d0eb-4b75-4bfe-8222-ba24f7a1cad4"
}
E remover um filme através da API:
curl -X DELETE http://localhost:8000/movies/bd02d0eb-4b75-4bfe-8222-ba24f7a1cad4/
Gosto bastante do Restless pela sua filosofia de manter-se simples. Mas nem tudo são rosas! A falta de autorização, HATEOAS e saídas em outros formatos (como XML, por exemplo) podem ser argumentos decisivos para a escolha de uma biblioteca mais completa.
Eu costumo utilizar o Restless em projetos muito específicos, onde sei que a API será sempre JSON, e integrações com o ORM trarão mais dores de cabeça do que soluções (em um cenário onde há interação com bancos NoSQL, o Restless se mostra uma ferramenta muito útil).
Confira o exemplo desse post no Github.
Até a próxima.