Introdução ao MongoDB e Tornado
O Tornado também facilita muito a criação de APIs JSON, e é assim que vamos usá-lo neste exemplo. O Motor, o driver assíncrono Python para MongoDB, vem com suporte integrado para Tornado, tornando o mais simples possível usar o MongoDB no Tornado, independentemente do tipo de servidor que você está construindo.
Neste início rápido, criaremos um aplicativo CRUD (Create, Read, Update, Delete) mostrando como você pode integrar o MongoDB com seus projetos do Tornado.
- Python 3.9.0
- Um cluster do MongoDB Atlas. Siga o guia "Introdução ao Atlas" para criar sua conta e o cluster do MongoDB. Anote seu nome de usuário, senha e string, pois você precisará deles mais tarde.
1 git clone git@github.com:mongodb-developer/mongodb-with-tornado.git
Você precisará instalar algumas dependências: Tornado, Motor, etc. Sempre recomendo que você instale todas as dependências do Python em um virtualenv para o projeto. Antes de executar o pip, verifique se o virtualenv está ativo.
1 cd mongodb-with-tornado 2 pip install -r requirements.txt
Pode levar alguns minutos para baixar e instalar suas dependências. Isso é normal, especialmente se você nunca instalou um pacote.
Depois de instalar as dependências, você precisará criar uma variável de ambiente para sua string de conexão do MongoDB.
1 export DB_URL="mongodb+srv://<username>:<password>@<url>/<db>?retryWrites=true&w=majority"
Lembre-se, sempre que você iniciar uma nova sessão de terminal, precisará definir essa variável de ambiente novamente. Eu uso direnv para tornar este processo mais fácil.
A etapa final é iniciar seu servidor Tornado.
1 python app.py
O Tornado não gera nada no terminal quando é iniciado, portanto, desde que você não tenha nenhuma mensagem de erro, seu servidor deve estar em execução.
Após o início do aplicativo, você pode visualizá-lo em seu navegador em http://127.0.0.1:8000/. Não haverá muito para ver no momento, pois você não tem nenhum dado! Veremos cada um dos pontos de conexão um pouco mais tarde no tutorial, mas se você quiser criar alguns dados agora para testar, você precisa enviar uma solicitação
POST
com um corpo JSON para o local URL.1 curl -X "POST" "http://localhost:8000/" \ 2 -H 'Accept: application/json' \ 3 -H 'Content-Type: application/json; charset=utf-8' \ 4 -d $'{ 5 "name": "Jane Doe", 6 "email": "jdoe@example.com", 7 "gpa": "3.9" 8 }'
Tente criar alguns alunos por meio destas
POST
solicitações e atualize seu navegador.Todo o código do aplicativo de exemplo está em
app.py
. Vou dividi-lo em seções e explicar o que cada uma está fazendo.Uma das primeiras coisas que fazemos é nos conectar ao nosso MongoDB database.
1 client = motor.motor_tornado.MotorClient(os.environ["MONGODB_URL"]) 2 db = client.college
Estamos usando o driver de motor assíncrono para criar nosso cliente MongoDB e, em seguida, especificamos o nome do nosso banco de dados
college
.Nosso aplicativo tem quatro rotas:
- POST / - cria um novo aluno.
- OBTER / - visualizar uma lista de todos os alunos ou de um único aluno.
- PUT /{id} - atualizar um aluno.
- DELETE /{id} - exclui um aluno.
Cada uma das rotas corresponde a um método na classe
MainHandler
. Veja como essa classe fica se mostrarmos apenas os stubs do método:1 class MainHandler(tornado.web.RequestHandler): 2 3 async def get(self, **kwargs): 4 pass 5 6 async def post(self): 7 pass 8 9 async def put(self, **kwargs): 10 pass 11 12 async def delete(self, **kwargs): 13 pass
Como você pode ver, os nomes dos métodos correspondem aos diferentes métodos
HTTP
. Vamos examinar cada método de cada vez.1 async def post(self): 2 student = tornado.escape.json_decode(self.request.body) 3 student["_id"] = str(ObjectId()) 4 5 new_student = await self.settings["db"]["students"].insert_one(student) 6 created_student = await self.settings["db"]["students"].find_one( 7 {"_id": new_student.inserted_id} 8 ) 9 10 self.set_status(201) 11 return self.write(created_student)
Observe como estou convertendo o
ObjectId
em uma string antes de atribuí-la como _id
. O MongoDB armazena dados como BSON, mas estamos codificando e decodificando nossos dados de strings JSON . O BSON tem suporte para outros tipos de dados não nativos do JSON, incluindo ObjectId
, mas o JSON não. Por isso, para simplificar, convertemos ObjectIds em strings antes de armazená-los.A rota recebe os dados do novo aluno como uma string JSON no corpo da solicitação
POST
. Decodificamos essa string de volta para um objeto Python antes de passá-la para nosso cliente MongoDB. Nosso cliente está disponível no dicionário de configurações porque o passamos para o Tornado quando criamos o aplicativo. Você pode ver isso no final doapp.py
.1 app = tornado.web.Application( 2 [ 3 (r"/", MainHandler), 4 (r"/(?P<student_id>\w+)", MainHandler), 5 ], 6 db=db, 7 )
A resposta do método
insert_one
inclui o_id
do aluno recém-criado. Depois de inserirmos o aluno em nossa collection, usamos o inserted_id
para encontrar o documento correto e escrevemos em nossa resposta. Por padrão, o Tornado retornará um código de status HTTP 200
, mas, nesse caso, um 201
criado é mais apropriado, por isso alteramos o código de status da resposta HTTP com set_status
.Temos duas maneiras diferentes de visualizar os dados dos alunos: como uma lista de todos os alunos ou como um documento de um único aluno. O método
get
lida com essas duas funções.1 async def get(self, student_id=None): 2 if student_id is not None: 3 if ( 4 student := await self.settings["db"]["students"].find_one( 5 {"_id": student_id} 6 ) 7 ) is not None: 8 return self.write(student) 9 else: 10 raise tornado.web.HTTPError(404) 11 else: 12 students = await self.settings["db"]["students"].find().to_list(1000) 13 return self.write({"students": students})
Primeiro, verificamos se a URL forneceu um parâmetro de caminho de
student_id
. Se sim, saberemos que estamos procurando um documento específico de um aluno. Procuramos o aluno correspondente com find_one
e ostudent_id
especificado . Se conseguirmos localizar um registro correspondente, ele será gravado na resposta como uma string JSON. Caso contrário, geramos um erro404
não encontrado.Se o URL não contiver um
student_id
, retornaremos uma lista de todos os alunos.O método
to_list
do Motor requer um argumento de contagem máxima de documentos. Para este exemplo, codifiquei-o para 1000
, mas em um aplicativo real, você usaria os parâmetros skip e limit em find para paginar seus resultados.É importante observar que, como proteção contra o sequestro de JSON, o Tornado não permitirá que você retorne uma array como elemento raiz. A maioria dos navegadores modernos corrige essa vulnerabilidade, mas o Tornado ainda erra por excesso de cautela. Portanto, devemos encapsular a array dos alunos em um dicionário antes de escrevê-la em nossa resposta.
1 async def put(self, student_id): 2 student = tornado.escape.json_decode(self.request.body) 3 await self.settings["db"]["students"].update_one( 4 {"_id": student_id}, {"$set": student} 5 ) 6 7 if ( 8 updated_student := await self.settings["db"]["students"].find_one( 9 {"_id": student_id} 10 ) 11 ) is not None: 12 return self.write(updated_student) 13 14 raise tornado.web.HTTPError(404)
A rota de atualização é como uma combinação das rotas de criação de aluno e de detalhamento do aluno. Ele recebe a ID do documento para atualizar
student_id
, bem como os novos dados no corpo JSON .Tentamos
$set
os novos valores no documento correto comupdate_one
e, em seguida, verificar se ele modificou corretamente um único documento. Se sim, encontramos o documento que acabou de ser atualizado e o retornamos.Se
modified_count
não for igual a um, ainda verificaremos se há um documento correspondente ao ID. Um modified_count
de zero pode MEAN que não há nenhum documento com esse ID, mas também pode MEAN que o documento existe, mas não precisou ser atualizado porque os valores atuais são os mesmos que os fornecidos na solicitaçãoPUT
.Somente depois que a localização final falhar, levantaremos uma exceção
404
Not Found.1 async def delete(self, student_id): 2 delete_result = await db["students"].delete_one({"_id": student_id}) 3 4 if delete_result.deleted_count == 1: 5 self.set_status(204) 6 return self.finish() 7 8 raise tornado.web.HTTPError(404)
Nossa rota final é
delete
. Novamente, como isso está afetando um único documento, temos que fornecer um id, student_id
no URL. Se encontrarmos um documento correspondente e o excluirmos com êxito, retornaremos um status HTTP de 204
ou Sem conteúdo. Nesse caso, não devolvemos um documento porque já o excluímos! No entanto, se não conseguirmos encontrar um aluno com o student_id
especificado, retornaremos um 404
.Esperemos que esta introdução ao Tornado com o MongoDB tenha sido útil para você. Agora é um momento surpreendente para os desenvolvedores de Python, pois mais e mais frameworks - novos e antigos - começam a aproveitar o assíncrono.
Se você quiser saber mais sobre como usar MongoDB o com Tornado e WebSockets, leia meu outro tutorial, Inscrever-se para MongoDB Change Streams receber do via WebSockets.
Se tiver dúvidas, acesse o site da nossa comunidade de desenvolvedores, no qual os engenheiros e a comunidade do MongoDB ajudarão você a desenvolver sua próxima grande ideia com o MongoDB.
Tecnologias Utilizadas
Linguagens