Artigo original: How to build a JSON API with Python
A especificação de APIs com JSON é uma maneira poderosa de habilitar a comunicação entre client e servidor. Ela especifica a estrutura das solicitações e respostas enviadas entre os dois, usando o formato JSON.
Como formato de dados, o JSON tem as vantagens de ser leve e legível. Isso torna muito fácil trabalhar com rapidez e produtividade. A especificação foi projetada para minimizar o número de solicitações e a quantidade de dados que precisam ser enviados entre o client e o servidor.
Aqui, você pode aprender a criar uma API em JSON básica usando Python e Flask. Em seguida, o resto do artigo mostrará como experimentar alguns dos recursos que a especificação da API em JSON tem a oferecer.
O Flask é uma biblioteca do Python que fornece um "microframework" para o desenvolvimento para a web. É ótimo para desenvolvimento rápido, pois vem com uma funcionalidade básica simples, mas extensível.
Um exemplo bastante simples de como enviar uma resposta em JSON usando Flask é vista abaixo:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def example():
return '{"name":"Bob"}'
if __name__ == '__main__':
app.run()
Este artigo usará dois add-ons para o Flask:
- Flask-REST-JSONAPI ajudará a desenvolver uma API que siga de perto a especificação de APIs em JSON.
- Flask-SQLAlchemy usará SQLAlchemy para tornar a criação e a interação com um banco de dados simples bastante rápida.
O cenário geral
O objetivo final é criar uma API que permita a interação do lado do client com um banco de dados subjacente. Haverá algumas camadas entre o banco de dados e o client – uma camada de abstração de dados e uma camada de gerenciador de recursos.
Aqui temos uma visão geral das etapas envolvidas:
- Defina um banco de dados usando o Flask-SQLAlchemy
- Crie uma abstração dos dados com Marshmallow-JSONAPI
- Crie os gerentes de recurso com a Flask-REST-JSONAPI
- Crie os endpoints de URL e inicie o servidor com o Flask
Este exemplo usará um esquema simples descrevendo artistas modernos e suas relações com diferentes obras de arte.
Instale tudo
Antes de começar, você precisará configurar o projeto. Isso envolve a criação de um espaço de trabalho e ambiente virtual, a instalação dos módulos necessários e a criação dos principais arquivos do Python e do banco de dados para o projeto.
Na linha de comando, crie um diretório e navegue para ele.
$ mkdir flask-jsonapi-demo
$ cd flask-jsonapi-demo/
É bom praticar a criação de ambientes virtuais (texto em inglês) para cada um de seus projetos em Python. Você pode ignorar essa etapa, mas ela é bastante recomendada.
$ python -m venv .venv
$ source .venv/bin/activate
Uma vez que seu ambiente virtual tenha sido criado e ativado, você pode instalar os módulos necessários para este projeto.
$ pip install flask-rest-jsonapi flask-sqlalchemy
Tudo o que você precisará será instalado conforme os requisitos para essas duas extensões. Isso inclui o próprio Flask e o SQLAlchemy.
A próxima etapa é criar um arquivo Python e um banco de dados para o projeto.
$ touch application.py artists.db
Crie o esquema do banco de dados
Aqui, você começa a modificar a application.py
para definir e criar o esquema do banco de dados para o projeto.
Abra application.py
no seu editor de texto favorito. Comece a importar os módulos. Para ser claro, os módulos serão importados conforme avançamos.
Em seguida, crie um objeto chamado app
como instância da classe Flask.
Depois, use o SQLAlchemy para conectar com o arquivo de banco de dados que você criou. A etapa final é definir e criar uma tabela chamada artists
.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# Criar a aplicação em Flask
app = Flask(__name__)
# Configurar o SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////artists.db'
db = SQLAlchemy(app)
# Definir uma classe para a tabela Artist
class Artist(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
birth_year = db.Column(db.Integer)
genre = db.Column(db.String)
# Criar a tabela
db.create_all()
Criação da camada de abstração
A próxima etapa usa o módulo Marshmallow-JSONAPI para criar uma abstração de dados lógica sobre as tabelas que acabamos de definir.
A razão para criar essa camada de abstração é simples. Ela oferece mais controle sobre como seus dados subjacentes são expostos por meio da API. Pense nessa camada como uma lente através da qual o consumidor da API pode visualizar os dados subjacentes claramente e apenas as partes que eles precisam ver.
No código abaixo, a camada de abstração de dados é definida como uma classe que herda da classe Schema
de Marshmallow-JSONAPI. Ela dará acesso por meio da API a registros únicos ou múltiplos da tabela artists
.
Neste bloco, a classe Meta
define alguns metadados. Especificamente, o nome do endpoint de URL para a interação com registros simples será artist_one
, onde cada artista será identificado com um parâmetro <id>
de URL. O nome do endpoint para a interação com diversos registros será artist_many
.
Os demais atributos definidos referem-se às colunas na tabela artists
. Aqui, você pode controlar ainda mais como cada um é exposto por meio da API.
Por exemplo, ao fazer solicitações de POST para adicionar novos artistas ao banco de dados, você pode garantir que o campo name
seja obrigatório definindo required=True
.
Se, por alguma razão, você não quiser que o campo birth_year
seja retornado ao fazer solicitações de GET, é possível especificar isso com load_only=True
.
from marshmallow_jsonapi.flask import Schema
from marshmallow_jsonapi import fields
# Criar a camada de abstração de dados
class ArtistSchema(Schema):
class Meta:
type_ = 'artist'
self_view = 'artist_one'
self_view_kwargs = {'id': '<id>'}
self_view_many = 'artist_many'
id = fields.Integer()
name = fields.Str(required=True)
birth_year = fields.Integer(load_only=True)
genre = fields.Str()
Criar os gerentes de recursos e os endpoints de URL
A peça final do quebra-cabeça é criar um gerente de recursos e o endpoint correspondente para cada uma das rotas, /artists
e /artists/id
.
Cada gerente de recursos é definido como uma classe que herda das classes ResourceList
e ResourceDetail
da Flask-REST-JSONAPI.
Eles recebem dois atributos. schema
é usado para indicar a camada de abstração de dados que o gerente de recursos utiliza, enquanto data_layer
indica a sessão e o modelo de dados que será usado para a camada de dados.
Em seguida, definimos api
como uma instância da classe Api
de Flask-REST-JSONAPI e criamos as rotas para a API com api.route()
. Esse método recebe três argumentos – a classe da camada de abstração de dados, o nome do endpoint e o caminho do URL.
A última etapa é escrever um loop principal para iniciar a aplicação no modo de depuração quando o script é executado diretamente. O modo de depuração é ótimo para desenvolvimento, mas não é adequado para execução em produção.
# Criar gerentes de recursos e endpoints
from flask_rest_jsonapi import Api, ResourceDetail, ResourceList
class ArtistMany(ResourceList):
schema = ArtistSchema
data_layer = {'session': db.session,
'model': Artist}
class ArtistOne(ResourceDetail):
schema = ArtistSchema
data_layer = {'session': db.session,
'model': Artist}
api = Api(app)
api.route(ArtistMany, 'artist_many', '/artists')
api.route(ArtistOne, 'artist_one', '/artists/<int:id>')
# loop principal para executar a aplicação em modo de depuração
if __name__ == '__main__':
app.run(debug=True)
Fazer solicitações de GET e de POST
Agora, você pode começar a usar a API para fazer solicitações em HTTP (texto em inglês). Isso pode ocorrer por meio de um navegador da web, com uma ferramenta de linha de comando, como o curl, ou a partir de outro programa (por exemplo, um script do Python usando a biblioteca Requests
).
Para iniciar o servidor, execute o script application.py
:
$ python application.py
No seu navegador, vá para http://localhost:5000/artists. Você verá um resultado em JSON de todos os registros no banco de dados até o momento. Porém, agora, o banco está vazio.
Para começar a adicionar registros ao banco de dados, você pode fazer uma solicitação de POST. Uma maneira de se fazer isso é a partir da linha de comando usando a ferramenta curl
. Como alternativa, você pode usar a ferramenta Insomnia ou criar uma interface de usuário simples em HTML que publique os dados usando um formulário.
Com o curl, na linha de comando, você pode inserir o seguinte:
curl -i -X POST -H 'Content-Type: application/json' -d '{"data":{"type":"artist", "attributes":{"name":"Salvador Dali", "birth_year":1904, "genre":"Surrealism"}}}' http://localhost:5000/artists
Se você navegar até http://localhost:5000/artists, verá o registro que acaba de adicionar. Se for adicionar mais registros, eles também aparecerão aqui, pois o caminho do URL chama o endpoint artists_many
.
Para ver apenas um único artista pelo id
, navegue até o URL relevante. Por exemplo, para ver o primeiro artista, experimente http://localhost:5000/artists/1.
Filtragem e ordenação
Um dos recursos interessantes da especificação de APIs em JSON é a capacidade de retornar a resposta de maneiras mais úteis, definindo alguns parâmetros no URL. Por exemplo, você pode classificar os resultados de acordo com um campo escolhido ou filtrar com base em alguns critérios.
O Flask-REST-JSONAPI vem com isso incorporado.
Para ordenar os artistas por ano de nascimento, navegue para http://localhost:5000/artists?sort=birth_year. Em uma aplicação da web, isso evitaria que você precisasse ordenar os resultados do lado do client, o que demandaria bastante em termos de desempenho e causaria um impacto na experiência do usuário.
A filtragem também é fácil. Anexe ao URL os critérios pelos quais você deseja filtrar, contidos por colchetes. Há três informações para incluir:
- "name" – o campo pelo qual você está filtrando (por exemplo,
birth_year
) - "op" - a operação de filtro ("maior que", "menor que", "igual a" etc.)
- "val" - o valor com relação à operação de filtro (por exemplo, 1900)
O URL abaixo filtra artistas cuja data de nascimento é após 1900:
http://localhost:5000/artists?filter=[{"name":"birth_year","op":"gt","val":1900}]
Essa funcionalidade torna muito mais fácil recuperar apenas informações relevantes ao chamar a API. Isso é valioso para melhorar o desempenho, especialmente ao recuperar volumes potencialmente grandes de dados em uma conexão lenta.
Paginação
Outro recurso da especificação da API em JSON que auxilia o desempenho é a paginação. É quando grandes respostas são enviadas em várias "páginas", em vez de todas de uma só vez. Você pode controlar o tamanho da página e o número da página solicitada no URL.
Assim, por exemplo, você pode receber 100 resultados em 10 páginas em vez de carregar todos os 100 de uma só vez. A primeira página conteria resultados de 1 a 10, a segunda página conteria resultados de 11 a 20 e assim por diante.
Para especificar o número de resultados que você deseja receber por página, você pode adicionar o parâmetro ?page[size]=X para o URL, onde X é o número de resultados por página. O Flask-REST-JSONAPI usa 30 como o tamanho de página padrão.
Para solicitar um determinado número de página, você pode adicionar o parâmetro ?page[number]=X, onde X é o número da página. Você pode combinar os dois parâmetros, como vemos aqui:
http://localhost:5000/artists?page[size]=2&page[number]=2
Esse URL define o tamanho da página como sendo de dois resultados por página, pedindo pela segunda página de resultados. Isso retornará o terceiro e o quarto resultados da resposta completa.
Relacionamentos
Quase sempre, os dados em uma tabela estarão relacionados aos dados armazenados em outra. Por exemplo, se você tem uma tabela de artistas, é provável que você também queira uma tabela de obras de arte. Cada obra está relacionada ao artista que a criou.
A especificação da API em JSON permite que você trabalhe com dados relacionais facilmente. O Flask-REST-JSONAPI permite que você aproveite isso. Aqui, isso será demonstrado adicionando uma tabela de obras de arte (artworks
) ao banco de dados e incluindo relacionamentos entre artista e obra de arte.
Para implementar o exemplo de obras de arte, será necessário fazer algumas alterações no código em application.py
.
Primeiro, fazemos algumas importações adicionais, e criamos uma tabela que relacione cada obra de arte a um artista:
from marshmallow_jsonapi.flask import Relationship
from flask_rest_jsonapi import ResourceRelationship
# Definir a tabela Artwork
class Artwork(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
artist_id = db.Column(db.Integer,
db.ForeignKey('artist.id'))
artist = db.relationship('Artist',
backref=db.backref('artworks'))
Em seguida, refazemos a camada de abstração:
# Criar a abstração de dados
class ArtistSchema(Schema):
class Meta:
type_ = 'artist'
self_view = 'artist_one'
self_view_kwargs = {'id': '<id>'}
self_view_many = 'artist_many'
id = fields.Integer()
name = fields.Str(required=True)
birth_year = fields.Integer(load_only=True)
genre = fields.Str()
artworks = Relationship(self_view = 'artist_artworks',
self_view_kwargs = {'id': '<id>'},
related_view = 'artwork_many',
many = True,
schema = 'ArtworkSchema',
type_ = 'artwork')
class ArtworkSchema(Schema):
class Meta:
type_ = 'artwork'
self_view = 'artwork_one'
self_view_kwargs = {'id': '<id>'}
self_view_many = 'artwork_many'
id = fields.Integer()
title = fields.Str(required=True)
artist_id = fields.Integer(required=True)
Isso define uma camada de abstração para a tabela artwork
e adiciona uma relação entre artista e obra de arte à classe ArtistSchema
.
Em seguida, defina novos gerentes de recursos para acessar várias obras de arte de uma vez e uma por vez, bem como para acessar os relacionamentos entre artista e obra de arte.
class ArtworkMany(ResourceList):
schema = ArtworkSchema
data_layer = {'session': db.session,
'model': Artwork}
class ArtworkOne(ResourceDetail):
schema = ArtworkSchema
data_layer = {'session': db.session,
'model': Artwork}
class ArtistArtwork(ResourceRelationship):
schema = ArtistSchema
data_layer = {'session': db.session,
'model': Artist}
Por fim, adicionamos alguns endpoints novos:
api.route(ArtworkOne, 'artwork_one', '/artworks/<int:id>')
api.route(ArtworkMany, 'artwork_many', '/artworks')
api.route(ArtistArtwork, 'artist_artworks',
'/artists/<int:id>/relationships/artworks')
Executamos application.py
e, ao tentar publicar alguns dados a partir da linha de comando usando a ferramenta curl
, temos:
curl -i -X POST -H 'Content-Type: application/json' -d '{"data":{"type":"artwork", "attributes":{"title":"The Persistance of Memory", "artist_id":1}}}' http://localhost:5000/artworks
Isso criará uma obra de arte relacionada ao artista com o id=1
.
No navegador, vá para http://localhost:5000/artists/1/relationships/artworks. Isso deve mostrar as obras relacionadas ao artista com id=1
. Isso evita que você escreva um URL mais complexo com parâmetros para filtrar trabalhos artísticos por seu campo artist_id
. Você pode listar rapidamente todas as relações entre um determinado artista e suas obras de arte.
Outro recurso é a capacidade de incluir resultados relacionados na resposta à chamada do endpoint artists_one
:
http://localhost:5000/artists/1?include=artworks
Isso retornará a resposta comum para o endpoint artists
, além dos resultados para cada uma das obras desse artista.
Campos esparsos
Um último recurso que vale a pena mencionar – campos esparsos. Ao trabalhar com grandes recursos de dados com muitos relacionamentos complexos, os tamanhos de resposta podem crescer muito rapidamente. É útil recuperar apenas os campos em que você está interessado.
A especificação da API em JSON permite que você faça isso adicionando um parâmetro fields
ao URL. Por exemplo, o URL abaixo obtém a resposta para um determinado artista e suas obras de arte relacionadas. No entanto, em vez de retornar todos os campos para a obra de arte fornecida, ele retorna apenas o título.
http://localhost:5000/artists/1?include=artworks&fields[artwork]=title
Isso é muito útil para melhorar o desempenho, especialmente em conexões lentas. Como regra geral, você só deve fazer solicitações de e para o servidor com a quantidade mínima de dados necessária.
Comentários finais
A especificação de APIs em JSON é um framework bastante útil para o envio de dados entre servidor e client em um formato limpo e flexível. Este artigo forneceu uma visão geral do que você pode fazer com ele, com um exemplo trabalhado em Python usando a biblioteca Flask-REST-JSONAPI.
Então, o que você vai fazer a seguir? As possibilidades são muitas. O exemplo neste artigo foi simples, com apenas duas tabelas e uma única relação entre elas. Você pode desenvolver uma aplicação tão sofisticada quanto quiser e criar uma API poderosa para interagir com a aplicação usando todas as ferramentas fornecidas aqui.
Obrigado pela leitura e uma ótima programação em Python para você!