1660168020
Neste tutorial, você aprenderá a desenvolver uma API assíncrona com FastAPI e MongoDB . Usaremos o pacote Motor para interagir com o MongoDB de forma assíncrona.
Ao final deste tutorial, você será capaz de:
Comece criando uma nova pasta para armazenar seu projeto chamada "fastapi-mongo":
$ mkdir fastapi-mongo
$ cd fastapi-mongo
Em seguida, crie e ative um ambiente virtual:
$ python3.9 -m venv venv
$ source venv/bin/activate
$ export PYTHONPATH=$PWD
Sinta-se à vontade para trocar virtualenv e Pip por Poetry ou Pipenv . Para saber mais, revise Ambientes Python Modernos .
Em seguida, crie os seguintes arquivos e pastas:
├── app
│ ├── __init__.py
│ ├── main.py
│ └── server
│ ├── app.py
│ ├── database.py
│ ├── models
│ └── routes
└── requirements.txt
Adicione as seguintes dependências ao seu arquivo requirements.txt :
fastapi==0.73.0
uvicorn==0.17.4
Instale-os:
(venv)$ pip install -r requirements.txt
No arquivo app/main.py , defina um ponto de entrada para executar o aplicativo:
import uvicorn
if __name__ == "__main__":
uvicorn.run("server.app:app", host="0.0.0.0", port=8000, reload=True)
Aqui, instruímos o arquivo para executar um servidor Uvicorn na porta 8000 e recarregar a cada alteração de arquivo.
Antes de iniciar o servidor por meio do arquivo de ponto de entrada, crie uma rota base em app/server/app.py :
from fastapi import FastAPI
app = FastAPI()
@app.get("/", tags=["Root"])
async def read_root():
return {"message": "Welcome to this fantastic app!"}
Tags são identificadores usados para agrupar rotas. As rotas com as mesmas tags são agrupadas em uma seção na documentação da API.
Execute o arquivo de ponto de entrada do seu console:
(venv)$ python app/main.py
Navegue até http://localhost:8000 em seu navegador. Você deveria ver:
{
"message": "Welcome to this fantastic app!"
}
Você também pode visualizar a documentação interativa da API em http://localhost:8000/docs :
Construiremos um aplicativo simples para armazenar dados de alunos com as seguintes rotas CRUD:
Antes de mergulharmos na escrita das rotas, vamos primeiro definir o esquema relevante e configurar o MongoDB.
Vamos definir o Schema para o qual nossos dados serão baseados, que representará como os dados são armazenados no banco de dados MongoDB.
Os esquemas Pydantic são usados para validar dados juntamente com serialização (JSON -> Python) e desserialização (Python -> JSON). Ele não serve como um validador de esquema do Mongo , em outras palavras.
Na pasta "app/server/models", crie um novo arquivo chamado student.py :
from typing import Optional
from pydantic import BaseModel, EmailStr, Field
class StudentSchema(BaseModel):
fullname: str = Field(...)
email: EmailStr = Field(...)
course_of_study: str = Field(...)
year: int = Field(..., gt=0, lt=9)
gpa: float = Field(..., le=4.0)
class Config:
schema_extra = {
"example": {
"fullname": "John Doe",
"email": "jdoe@x.edu.ng",
"course_of_study": "Water resources engineering",
"year": 2,
"gpa": "3.0",
}
}
class UpdateStudentModel(BaseModel):
fullname: Optional[str]
email: Optional[EmailStr]
course_of_study: Optional[str]
year: Optional[int]
gpa: Optional[float]
class Config:
schema_extra = {
"example": {
"fullname": "John Doe",
"email": "jdoe@x.edu.ng",
"course_of_study": "Water resources and environmental engineering",
"year": 4,
"gpa": "4.0",
}
}
def ResponseModel(data, message):
return {
"data": [data],
"code": 200,
"message": message,
}
def ErrorResponseModel(error, code, message):
return {"error": error, "code": code, "message": message}
No código acima, definimos um Pydantic Schema chamado StudentSchema
que representa como os dados do aluno serão armazenados em seu banco de dados MongoDB.
Em Pydantic, as reticências , ...
, indicam que um campo é obrigatório. Ele pode ser substituído por None
ou por um valor padrão. Em StudentSchema
, cada campo tem reticências, pois cada campo é importante e o programa não deve prosseguir sem ter os valores configurados.
No campo gpa
e do , adicionamos os validadores , , e :yearStudentSchema
gtltle
gt
e lt
no year
campo garante que o valor passado seja maior que 0 e menor que 9 . Como resultado, valores como 0 , 10 , 11 , resultarão em erros.le
validador no gpa
campo garante que o valor passado seja menor ou igual a 4.0 .Esse esquema ajudará os usuários a enviar solicitações HTTP com a forma adequada para a API -- ou seja, o tipo de dados a serem enviados e como enviá-los.
FastAPI usa Pyantic Schemas para documentar automaticamente modelos de dados em conjunto com Json Schema . A interface do usuário do Swagger renderiza os dados dos modelos de dados gerados. Você pode ler mais sobre como o FastAPI gera a documentação da API aqui .
Como usamos EmailStr
o , precisamos instalar o email-validator .
Adicione-o ao arquivo de requisitos:
pydantic[email]
Instalar:
(venv)$ pip install -r requirements.txt
Com o esquema em vigor, vamos configurar o MongoDB antes de escrever as rotas para a API.
Nesta seção, conectaremos o MongoDB e configuraremos nosso aplicativo para se comunicar com ele.
De acordo com a Wikipedia , o MongoDB é um programa de banco de dados orientado a documentos multiplataforma. Classificado como um programa de banco de dados NoSQL, o MongoDB usa documentos semelhantes a JSON com esquemas opcionais.
Se você não tiver o MongoDB instalado em sua máquina, consulte o guia de instalação da documentação. Uma vez instalado, continue com o guia para executar o processo do mongod daemon. Uma vez feito, você pode verificar se o MongoDB está funcionando, conectando-se à instância por meio do mongo
comando shell:
$ mongo
Para referência, este tutorial usa o MongoDB Community Edition v5.0.6.
$ mongo --version
MongoDB shell version v5.0.6
Build Info: {
"version": "5.0.6",
"gitVersion": "212a8dbb47f07427dae194a9c75baec1d81d9259",
"modules": [],
"allocator": "system",
"environment": {
"distarch": "x86_64",
"target_arch": "x86_64"
}
}
Em seguida, vamos configurar o Motor , um driver assíncrono do MongoDB, para interagir com o banco de dados.
Comece adicionando a dependência ao arquivo de requisitos:
motor==2.5.1
Instalar:
(venv)$ pip install -r requirements.txt
De volta ao aplicativo, adicione as informações de conexão do banco de dados a app/server/database.py :
import motor.motor_asyncio
MONGO_DETAILS = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_DETAILS)
database = client.students
student_collection = database.get_collection("students_collection")
No código acima, importamos Motor, definimos os detalhes da conexão e criamos um cliente via AsyncIOMotorClient .
Em seguida, referenciamos um banco de dados chamado students
e uma coleção (semelhante a uma tabela em um banco de dados relacional) chamada students_collection
. Como essas são apenas referências e não E/S reais, nenhuma delas requer uma await
expressão. Quando a primeira operação de E/S for feita, o banco de dados e a coleção serão criados, caso ainda não existam.
Em seguida, crie uma função auxiliar rápida para analisar os resultados de uma consulta de banco de dados em um dict do Python.
Adicione isso ao arquivo database.py também:
import motor.motor_asyncio
MONGO_DETAILS = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_DETAILS)
database = client.students
student_collection = database.get_collection("students_collection")
# helpers
def student_helper(student) -> dict:
return {
"id": str(student["_id"]),
"fullname": student["fullname"],
"email": student["email"],
"course_of_study": student["course_of_study"],
"year": student["year"],
"GPA": student["gpa"],
}
Em seguida, vamos escrever as operações do banco de dados CRUD.
Comece importando o ObjectId
método do pacote bson na parte superior do arquivo database.py :
from bson.objectid import ObjectId
bson vem instalado como uma dependência do motor.
Em seguida, adicione cada uma das seguintes funções para as operações CRUD:
# Retrieve all students present in the database
async def retrieve_students():
students = []
async for student in student_collection.find():
students.append(student_helper(student))
return students
# Add a new student into to the database
async def add_student(student_data: dict) -> dict:
student = await student_collection.insert_one(student_data)
new_student = await student_collection.find_one({"_id": student.inserted_id})
return student_helper(new_student)
# Retrieve a student with a matching ID
async def retrieve_student(id: str) -> dict:
student = await student_collection.find_one({"_id": ObjectId(id)})
if student:
return student_helper(student)
# Update a student with a matching ID
async def update_student(id: str, data: dict):
# Return false if an empty request body is sent.
if len(data) < 1:
return False
student = await student_collection.find_one({"_id": ObjectId(id)})
if student:
updated_student = await student_collection.update_one(
{"_id": ObjectId(id)}, {"$set": data}
)
if updated_student:
return True
return False
# Delete a student from the database
async def delete_student(id: str):
student = await student_collection.find_one({"_id": ObjectId(id)})
if student:
await student_collection.delete_one({"_id": ObjectId(id)})
return True
No código acima, definimos as operações assíncronas para criar, ler, atualizar e excluir os dados do aluno no banco de dados via motor.
Nas operações de atualização e exclusão, o aluno é procurado no banco de dados para decidir se realiza ou não a operação. Os valores de retorno orientam como enviar respostas ao usuário em que trabalharemos na próxima seção.
Nesta seção, adicionaremos as rotas para complementar as operações do banco de dados no arquivo de banco de dados.
Na pasta "routes", crie um novo arquivo chamado student.py e adicione o seguinte conteúdo a ele:
from fastapi import APIRouter, Body
from fastapi.encoders import jsonable_encoder
from app.server.database import (
add_student,
delete_student,
retrieve_student,
retrieve_students,
update_student,
)
from app.server.models.student import (
ErrorResponseModel,
ResponseModel,
StudentSchema,
UpdateStudentModel,
)
router = APIRouter()
Usaremos o codificador compatível com JSON da FastAPI para converter nossos modelos em um formato compatível com JSON.
Em seguida, conecte a rota do aluno em app/server/app.py :
from fastapi import FastAPI
from app.server.routes.student import router as StudentRouter
app = FastAPI()
app.include_router(StudentRouter, tags=["Student"], prefix="/student")
@app.get("/", tags=["Root"])
async def read_root():
return {"message": "Welcome to this fantastic app!"}
De volta ao arquivo de rotas, adicione o seguinte manipulador para criar um novo aluno:
@router.post("/", response_description="Student data added into the database")
async def add_student_data(student: StudentSchema = Body(...)):
student = jsonable_encoder(student)
new_student = await add_student(student)
return ResponseModel(new_student, "Student added successfully.")
Portanto, a rota espera uma carga útil que corresponda ao formato de StudentSchema
. Exemplo:
{
"fullname": "John Doe",
"email": "jdoe@x.edu.ng",
"course_of_study": "Water resources engineering",
"year": 2,
"gpa": "3.0",
}
Inicie o servidor Uvicorn:
(venv)$ python app/main.py
E atualize a página de documentação da API interativa em http://localhost:8000/docs para ver a nova rota:
Faça o teste também:
Portanto, quando uma solicitação é enviada ao endpoint, ele armazena um corpo de solicitação codificado em JSON na variável student
antes de chamar o add_student
método de banco de dados e armazenar a resposta na new_student
variável. A resposta do banco de dados é então retornada por meio do arquivo ResponseModel
.
Teste também os validadores:
Seguindo em frente, adicione as seguintes rotas para recuperar todos os alunos e um único aluno:
@router.get("/", response_description="Students retrieved")
async def get_students():
students = await retrieve_students()
if students:
return ResponseModel(students, "Students data retrieved successfully")
return ResponseModel(students, "Empty list returned")
@router.get("/{id}", response_description="Student data retrieved")
async def get_student_data(id):
student = await retrieve_student(id)
if student:
return ResponseModel(student, "Student data retrieved successfully")
return ErrorResponseModel("An error occurred.", 404, "Student doesn't exist.")
O que acontece se você não passar um ObjectId válido -- por exemplo,
1
-- para o ID para recuperar uma única rota de aluno? Como você pode lidar melhor com isso no aplicativo?
Quando a operação de exclusão for implementada, você terá a oportunidade de testar a resposta para um banco de dados vazio.
Em seguida, escreva a rota individual para atualizar os dados do aluno:
@router.put("/{id}")
async def update_student_data(id: str, req: UpdateStudentModel = Body(...)):
req = {k: v for k, v in req.dict().items() if v is not None}
updated_student = await update_student(id, req)
if updated_student:
return ResponseModel(
"Student with ID: {} name update is successful".format(id),
"Student name updated successfully",
)
return ErrorResponseModel(
"An error occurred",
404,
"There was an error updating the student data.",
)
Por fim, adicione a rota de exclusão:
@router.delete("/{id}", response_description="Student data deleted from the database")
async def delete_student_data(id: str):
deleted_student = await delete_student(id)
if deleted_student:
return ResponseModel(
"Student with ID: {} removed".format(id), "Student deleted successfully"
)
return ErrorResponseModel(
"An error occurred", 404, "Student with id {0} doesn't exist".format(id)
)
Recupere o ID do usuário que você criou anteriormente e teste a rota de exclusão:
Remova todos os alunos restantes e teste as rotas de leitura novamente, garantindo que as respostas sejam apropriadas para um banco de dados vazio.
Nesta seção, implantaremos o aplicativo no Heroku e configuraremos um banco de dados em nuvem para o MongoDB.
Antes de implantar, precisamos configurar o MongoDB Atlas , um serviço de banco de dados em nuvem para o MongoDB hospedar nosso banco de dados.
Siga o guia de introdução onde você criará uma conta, implantará um cluster de camada gratuita, configurará um usuário e colocará um endereço IP na lista de permissões.
Para fins de teste, use
0.0.0.0/0
para o IP da lista de permissões para permitir o acesso de qualquer lugar. Para um aplicativo de produção, você desejará restringir o acesso a um IP estático.
Uma vez feito, pegue as informações de conexão do banco de dados do seu cluster clicando no botão "Conectar":
Clique na segunda opção, "Conectar ao seu aplicativo":
Copie o URL de conexão, certificando-se de atualizar a senha. Defina o banco de dados padrão para "alunos" também. Será semelhante a:
mongodb+srv://foobar:foobar@cluster0.0reol.mongodb.net/students?retryWrites=true&w=majority
Em vez de codificar esse valor em nosso aplicativo, vamos defini-lo como uma variável de ambiente. Crie um novo arquivo chamado .env na raiz do projeto e as informações de conexão para ele:
MONGO_DETAILS=your_connection_url
Certifique-se de substituir
your_connection_url
pelo URL copiado.
Em seguida, para simplificar o gerenciamento de variáveis de ambiente em nosso aplicativo, vamos instalar o pacote Python Decouple . Adicione-o ao seu arquivo de requisitos assim:
python-decouple==3.6
Instalar:
(venv)$ pip install -r requirements.txt
No arquivo app/server/database.py , importe a biblioteca:
from decouple import config
O método importado config
varre o diretório raiz em busca de um arquivo .env e lê o conteúdo passado para ele. Então, no nosso caso, ele vai ler a MONGO_DETAILS
variável.
Em seguida, altere a MONGO_DETAILS
variável para:
MONGO_DETAILS = config("MONGO_DETAILS") # read environment variable
Antes de implantar, vamos testar o aplicativo localmente com o banco de dados em nuvem para garantir que a conexão esteja configurada corretamente. Reinicie seu servidor Uvicorn e teste cada rota da documentação interativa em http://localhost:8000/docs .
Você deve conseguir ver os dados no painel do Atlas:
Por fim, vamos implantar o aplicativo no Heroku .
Heroku é uma plataforma de nuvem como serviço (PaaS) usada para implantar e dimensionar aplicativos.
Se necessário, inscreva-se em uma conta Heroku e instale o Heroku CLI .
Antes de continuar, crie um arquivo .gitignore no projeto para evitar o check-in da pasta "venv" e do arquivo .env no git:
(venv)$ touch .gitignore
Adicione o seguinte:
.env
venv/
__pycache__
Em seguida, adicione um Procfile à raiz do seu projeto:
web: uvicorn app.server.app:app --host 0.0.0.0 --port=$PORT
Notas:
web
junto com o comando para servir o Uvicorn.$PORT
variável de ambiente.Seu projeto agora deve ter os seguintes arquivos e pastas:
├── .env
├── .gitignore
├── Procfile
├── app
│ ├── __init__.py
│ ├── main.py
│ └── server
│ ├── app.py
│ ├── database.py
│ ├── models
│ │ └── student.py
│ └── routes
│ └── student.py
└── requirements.txt
Na raiz do seu projeto, inicialize um novo repositório git:
(venv)$ git init
(venv)$ git add .
(venv)$ git commit -m "My fastapi and mongo application"
Agora, podemos criar um novo aplicativo no Heroku:
(venv)$ heroku create
Além de criar um novo aplicativo, esse comando cria um repositório git remoto no Heroku para enviarmos nosso aplicativo para implantação. Em seguida, ele define isso como um controle remoto no repositório local automaticamente para nós.
Você pode verificar se o controle remoto está configurado executando
git remote -v
.
Anote o URL do seu aplicativo.
Como não adicionamos o arquivo .env ao git, precisamos definir a variável de ambiente no ambiente Heroku:
(venv)$ heroku config:set MONGO_DETAILS="your_mongo_connection_url"
Novamente, certifique-se de substituir
your_connection_url
pelo URL de conexão real.
Envie seu código para o Heroku e certifique-se de que pelo menos uma instância do aplicativo esteja em execução:
(venv)$ git push heroku master
(venv)$ heroku ps:scale web=1
Execute heroku open
para abrir seu aplicativo em seu navegador padrão.
Você implantou com sucesso seu aplicativo no Heroku. Teste-o.
Neste tutorial, você aprendeu como criar um aplicativo CRUD com FastAPI e MongoDB e implantá-lo no Heroku. Faça uma autoverificação rápida revisando os objetivos no início do tutorial. Você pode encontrar o código usado neste tutorial no GitHub .
Fonte: https://testdrive.io
#crud #fastapi #mongodb #heroku
1656756547
Neste tutorial, você aprenderá a desenvolver uma API assíncrona com FastAPI e MongoDB . Usaremos a biblioteca Beanie ODM para interagir com o MongoDB de forma assíncrona.
Ao final deste tutorial, você será capaz de:
Beanie é um mapeador de documento-objeto (ODM) assíncrono para MongoDB, que suporta migrações de dados e esquemas prontas para uso. Ele usa Motor , como um mecanismo de banco de dados assíncrono, e Pydantic .
Embora você possa simplesmente usar o Motor, o Beanie fornece uma camada de abstração adicional, tornando muito mais fácil interagir com coleções dentro de um banco de dados Mongo.
Quer apenas usar o Motor? Confira Construindo um aplicativo CRUD com FastAPI e MongoDB .
Comece criando uma nova pasta para armazenar seu projeto chamada "fastapi-beanie":
$ mkdir fastapi-beanie
$ cd fastapi-beanie
Em seguida, crie e ative um ambiente virtual:
$ python3.10 -m venv venv
$ source venv/bin/activate
(venv)$ export PYTHONPATH=$PWD
Sinta-se à vontade para trocar venv e Pip por Poetry ou Pipenv . Para saber mais, revise Ambientes Python Modernos .
Em seguida, crie os seguintes arquivos e pastas:
├── app
│ ├── __init__.py
│ ├── main.py
│ └── server
│ ├── app.py
│ ├── database.py
│ ├── models
│ └── routes
└── requirements.txt
Adicione as seguintes dependências ao seu arquivo requirements.txt :
beanie==1.11.0
fastapi==0.78.0
uvicorn==0.17.6
Instale as dependências do seu terminal:
(venv)$ pip install -r requirements.txt
No arquivo app/main.py , defina um ponto de entrada para executar o aplicativo:
import uvicorn
if __name__ == "__main__":
uvicorn.run("server.app:app", host="0.0.0.0", port=8000, reload=True)
Aqui, instruímos o arquivo para executar um servidor Uvicorn na porta 8000 e recarregar a cada alteração de arquivo.
Antes de iniciar o servidor por meio do arquivo de ponto de entrada, crie uma rota base em app/server/app.py :
from fastapi import FastAPI
app = FastAPI()
@app.get("/", tags=["Root"])
async def read_root() -> dict:
return {"message": "Welcome to your beanie powered app!"}
Execute o arquivo de ponto de entrada do seu console:
(venv)$ python app/main.py
Navegue até http://localhost:8000 em seu navegador. Você deveria ver:
{
"message": "Welcome to your beanie powered app!"
}
Construiremos um aplicativo de análise de produtos que nos permitirá realizar as seguintes operações:
Antes de mergulhar na escrita das rotas, vamos usar o Beanie para configurar o modelo de banco de dados para nosso aplicativo.
Beanie permite que você crie documentos que podem ser usados para interagir com coleções no banco de dados. Documentos representam seu esquema de banco de dados. Eles podem ser definidos criando classes filhas que herdam a Document
classe de Beanie. A Document
classe é alimentada pelo Pydantic's BaseModel
, o que facilita a definição de coleções e esquema de banco de dados, bem como dados de exemplo exibidos na página interativa de documentos do Swagger.
Exemplo:
from beanie import Document
class TestDrivenArticle(Document):
title: str
content: str
date: datetime
author: str
O documento definido representa como os artigos serão armazenados no banco de dados. No entanto, é uma classe de documento normal sem nenhuma coleção de banco de dados associada a ela. Para associar uma coleção, basta adicionar uma Settings
classe como subclasse:
from beanie import Document
class TestDrivenArticle(Document):
title: str
content: str
date: datetime
author: str
class Settings:
name = "testdriven_collection"
Agora que temos uma ideia de como os esquemas são criados, vamos criar o esquema para nossa aplicação. Na pasta "app/server/models", crie um novo arquivo chamado product_review.py :
from datetime import datetime
from beanie import Document
from pydantic import BaseModel
from typing import Optional
class ProductReview(Document):
name: str
product: str
rating: float
review: str
date: datetime = datetime.now()
class Settings:
name = "product_review"
Como a Document
classe é desenvolvida pelo Pydantic, podemos definir dados de esquema de exemplo para tornar mais fácil para os desenvolvedores usarem a API dos documentos interativos do Swagger.
Adicione a Config
subclasse assim:
from datetime import datetime
from beanie import Document
from pydantic import BaseModel
from typing import Optional
class ProductReview(Document):
name: str
product: str
rating: float
review: str
date: datetime = datetime.now()
class Settings:
name = "product_review"
class Config:
schema_extra = {
"example": {
"name": "Abdulazeez",
"product": "TestDriven TDD Course",
"rating": 4.9,
"review": "Excellent course!",
"date": datetime.now()
}
}
Assim, no bloco de código acima, definimos um documento chamado Beanie ProductReview
que representa como uma revisão de produto será armazenada. Também definimos a coleta, product_review
, onde os dados serão armazenados.
Usaremos esse esquema na rota para impor o corpo da solicitação adequado.
Por fim, vamos definir o esquema para atualizar uma revisão de produto:
class UpdateProductReview(BaseModel):
name: Optional[str]
product: Optional[str]
rating: Optional[float]
review: Optional[str]
date: Optional[datetime]
class Config:
schema_extra = {
"example": {
"name": "Abdulazeez Abdulazeez",
"product": "TestDriven TDD Course",
"rating": 5.0,
"review": "Excellent course!",
"date": datetime.now()
}
}
A UpdateProductReview
classe acima é do tipo BaseModel , o que nos permite fazer alterações apenas nos campos presentes no corpo da requisição.
Com o esquema em vigor, vamos configurar o MongoDB e nosso banco de dados antes de continuar a escrever as rotas.
Nesta seção, conectaremos o MongoDB e configuraremos nosso aplicativo para se comunicar com ele.
De acordo com a Wikipedia , o MongoDB é um programa de banco de dados orientado a documentos multiplataforma. Classificado como um programa de banco de dados NoSQL, o MongoDB usa documentos semelhantes a JSON com esquemas opcionais.
Se você não tiver o MongoDB instalado em sua máquina, consulte o guia de instalação da documentação. Uma vez instalado, continue com o guia para executar o processo do mongod daemon. Uma vez feito, você pode verificar se o MongoDB está funcionando, conectando-se à instância por meio do mongo
comando shell:
$ mongo
Para referência, este tutorial usa o MongoDB Community Edition v5.0.7.
$ mongo --version
MongoDB shell version v5.0.7
Build Info: {
"version": "5.0.7",
"gitVersion": "b977129dc70eed766cbee7e412d901ee213acbda",
"modules": [],
"allocator": "system",
"environment": {
"distarch": "x86_64",
"target_arch": "x86_64"
}
}
Em database.py , adicione o seguinte:
from beanie import init_beanie
import motor.motor_asyncio
from app.server.models.product_review import ProductReview
async def init_db():
client = motor.motor_asyncio.AsyncIOMotorClient(
"mongodb://localhost:27017/productreviews"
)
await init_beanie(database=client.db_name, document_models=[ProductReview])
No bloco de código acima, importamos o método init_beanie que é responsável por inicializar o mecanismo de banco de dados alimentado por motor.motor_asyncio . O init_beanie
método recebe dois argumentos:
database
- O nome do banco de dados a ser usado.document_models
- Uma lista de modelos de documentos definidos -- o ProductReview
modelo, no nosso caso.A init_db
função será invocada no evento de inicialização do aplicativo. Atualize app.py para incluir o evento de inicialização:
from fastapi import FastAPI
from app.server.database import init_db
app = FastAPI()
@app.on_event("startup")
async def start_db():
await init_db()
@app.get("/", tags=["Root"])
async def read_root() -> dict:
return {"message": "Welcome to your beanie powered app!"}
Agora que temos nossas configurações de banco de dados, vamos escrever as rotas.
Nesta seção, construiremos as rotas para realizar operações CRUD em seu banco de dados a partir do aplicativo:
Na pasta "routes", crie um arquivo chamado product_review.py :
from beanie import PydanticObjectId
from fastapi import APIRouter, HTTPException
from typing import List
from app.server.models.product_review import ProductReview, UpdateProductReview
router = APIRouter()
No bloco de código acima, importamos PydanticObjectId
, que será usado para indicar o tipo do argumento ID ao recuperar uma única solicitação. Também importamos a APIRouter
classe responsável por lidar com as operações de rota. Também importamos a classe de modelo que definimos anteriormente.
Os modelos de documentos Beanie nos permitem interagir diretamente com o banco de dados com menos código. Por exemplo, para recuperar todos os registros em uma coleção de banco de dados, tudo o que precisamos fazer é:
data = await ProductReview.find_all().to_list()
return data # A list of all records in the collection.
Antes de continuarmos a escrever a função de rota para as operações CRUD, vamos registrar a rota em app.py :
from fastapi import FastAPI
from app.server.database import init_db
from app.server.routes.product_review import router as Router
app = FastAPI()
app.include_router(Router, tags=["Product Reviews"], prefix="/reviews")
@app.on_event("startup")
async def start_db():
await init_db()
@app.get("/", tags=["Root"])
async def read_root() -> dict:
return {"message": "Welcome to your beanie powered app!"}
Em routes/product_review.py , adicione o seguinte:
@router.post("/", response_description="Review added to the database")
async def add_product_review(review: ProductReview) -> dict:
await review.create()
return {"message": "Review added successfully"}
Aqui, definimos a função de rota, que recebe um argumento do tipo ProductReview
. Como dito anteriormente, a classe de documento pode interagir diretamente com o banco de dados.
O novo registro é criado chamando o método create() .
A rota acima espera uma carga semelhante a esta:
{
"name": "Abdulazeez",
"product": "TestDriven TDD Course",
"rating": 4.9,
"review": "Excellent course!",
"date": "2022-05-17T13:53:17.196135"
}
Teste a rota:
$ curl -X 'POST' \
'http://0.0.0.0:8000/reviews/' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"name": "Abdulazeez",
"product": "TestDriven TDD Course",
"rating": 4.9,
"review": "Excellent course!",
"date": "2022-05-17T13:53:17.196135"
}'
A solicitação acima deve retornar uma mensagem de sucesso:
{
"message": "Review added successfully"
}
A seguir estão as rotas que nos permitem recuperar uma única revisão e todas as revisões presentes no banco de dados:
@router.get("/{id}", response_description="Review record retrieved")
async def get_review_record(id: PydanticObjectId) -> ProductReview:
review = await ProductReview.get(id)
return review
@router.get("/", response_description="Review records retrieved")
async def get_reviews() -> List[ProductReview]:
reviews = await ProductReview.find_all().to_list()
return reviews
No bloco de código acima, definimos duas funções:
ObjectiD
, a codificação padrão para IDs do MongoDB. O registro é recuperado usando o método get() .to_list()
método é anexado para que os resultados sejam retornados em uma lista.Outro método que pode ser usado para recuperar uma única entrada é o método find_one() que recebe uma condição. Por exemplo:
# Return a record who has a rating of 4.0 await ProductReview.find_one(ProductReview.rating == 4.0)
Vamos testar a primeira rota para recuperar todos os registros:
$ curl -X 'GET' \
'http://0.0.0.0:8000/reviews/' \
-H 'accept: application/json'
Resposta:
[
{
"_id": "62839ad1d9a88a040663a734",
"name": "Abdulazeez",
"product": "TestDriven TDD Course",
"rating": 4.9,
"review": "Excellent course!",
"date": "2022-05-17T13:53:17.196000"
}
]
Em seguida, vamos testar a rota para recuperar um único registro que corresponda a um ID fornecido:
$ curl -X 'GET' \
'http://0.0.0.0:8000/reviews/62839ad1d9a88a040663a734' \
-H 'accept: application/json'
Resposta:
{
"_id": "62839ad1d9a88a040663a734",
"name": "Abdulazeez",
"product": "TestDriven TDD Course",
"rating": 4.9,
"review": "Excellent course!",
"date": "2022-05-17T13:53:17.196000"
}
Em seguida, vamos escrever a rota para atualizar o registro de revisão:
@router.put("/{id}", response_description="Review record updated")
async def update_student_data(id: PydanticObjectId, req: UpdateProductReview) -> ProductReview:
req = {k: v for k, v in req.dict().items() if v is not None}
update_query = {"$set": {
field: value for field, value in req.items()
}}
review = await ProductReview.get(id)
if not review:
raise HTTPException(
status_code=404,
detail="Review record not found!"
)
await review.update(update_query)
return review
Nesta função, filtramos os campos que não são atualizados para evitar a substituição de campos existentes por None
.
Para atualizar um registro, é necessária uma consulta de atualização. Definimos uma consulta de atualização que substitui os campos existentes pelos dados passados no corpo da solicitação. Em seguida, verificamos se o registro existe. Se existir, ele é atualizado e o registro atualizado é retornado, caso contrário, uma exceção 404 é gerada.
Vamos testar a rota:
$ curl -X 'PUT' \
'http://0.0.0.0:8000/reviews/62839ad1d9a88a040663a734' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"name": "Abdulazeez Abdulazeez",
"product": "TestDriven TDD Course",
"rating": 5
}'
Resposta:
{
"_id": "62839ad1d9a88a040663a734",
"name": "Abdulazeez Abdulazeez",
"product": "TestDriven TDD Course",
"rating": 5.0,
"review": "Excellent course!",
"date": "2022-05-17T13:53:17.196000"
}
Por fim, vamos escrever a rota responsável por deletar um registro:
@router.delete("/{id}", response_description="Review record deleted from the database")
async def delete_student_data(id: PydanticObjectId) -> dict:
record = await ProductReview.get(id)
if not record:
raise HTTPException(
status_code=404,
detail="Review record not found!"
)
await record.delete()
return {
"message": "Record deleted successfully"
}
Portanto, primeiro verificamos se o registro existe antes de excluir o registro. O registro é excluído chamando o método delete() .
Vamos testar a rota:
$ curl -X 'DELETE' \
'http://0.0.0.0:8000/reviews/62839ad1d9a88a040663a734' \
-H 'accept: application/json'
Resposta:
{
"message": "Record deleted successfully"
}
Construímos com sucesso um aplicativo CRUD desenvolvido com FastAPI, MongoDB e Beanie ODM.
Neste tutorial, você aprendeu como criar um aplicativo CRUD com FastAPI, MongoDB e Beanie ODM. Faça uma autoverificação rápida revisando os objetivos no início do tutorial, você pode encontrar o código usado neste tutorial no GitHub .
Fonte: https://testdrive.io
1595396220
As more and more data is exposed via APIs either as API-first companies or for the explosion of single page apps/JAMStack, API security can no longer be an afterthought. The hard part about APIs is that it provides direct access to large amounts of data while bypassing browser precautions. Instead of worrying about SQL injection and XSS issues, you should be concerned about the bad actor who was able to paginate through all your customer records and their data.
Typical prevention mechanisms like Captchas and browser fingerprinting won’t work since APIs by design need to handle a very large number of API accesses even by a single customer. So where do you start? The first thing is to put yourself in the shoes of a hacker and then instrument your APIs to detect and block common attacks along with unknown unknowns for zero-day exploits. Some of these are on the OWASP Security API list, but not all.
Most APIs provide access to resources that are lists of entities such as /users
or /widgets
. A client such as a browser would typically filter and paginate through this list to limit the number items returned to a client like so:
First Call: GET /items?skip=0&take=10
Second Call: GET /items?skip=10&take=10
However, if that entity has any PII or other information, then a hacker could scrape that endpoint to get a dump of all entities in your database. This could be most dangerous if those entities accidently exposed PII or other sensitive information, but could also be dangerous in providing competitors or others with adoption and usage stats for your business or provide scammers with a way to get large email lists. See how Venmo data was scraped
A naive protection mechanism would be to check the take count and throw an error if greater than 100 or 1000. The problem with this is two-fold:
skip = 0
while True: response = requests.post('https://api.acmeinc.com/widgets?take=10&skip=' + skip), headers={'Authorization': 'Bearer' + ' ' + sys.argv[1]}) print("Fetched 10 items") sleep(randint(100,1000)) skip += 10
To secure against pagination attacks, you should track how many items of a single resource are accessed within a certain time period for each user or API key rather than just at the request level. By tracking API resource access at the user level, you can block a user or API key once they hit a threshold such as “touched 1,000,000 items in a one hour period”. This is dependent on your API use case and can even be dependent on their subscription with you. Like a Captcha, this can slow down the speed that a hacker can exploit your API, like a Captcha if they have to create a new user account manually to create a new API key.
Most APIs are protected by some sort of API key or JWT (JSON Web Token). This provides a natural way to track and protect your API as API security tools can detect abnormal API behavior and block access to an API key automatically. However, hackers will want to outsmart these mechanisms by generating and using a large pool of API keys from a large number of users just like a web hacker would use a large pool of IP addresses to circumvent DDoS protection.
The easiest way to secure against these types of attacks is by requiring a human to sign up for your service and generate API keys. Bot traffic can be prevented with things like Captcha and 2-Factor Authentication. Unless there is a legitimate business case, new users who sign up for your service should not have the ability to generate API keys programmatically. Instead, only trusted customers should have the ability to generate API keys programmatically. Go one step further and ensure any anomaly detection for abnormal behavior is done at the user and account level, not just for each API key.
APIs are used in a way that increases the probability credentials are leaked:
If a key is exposed due to user error, one may think you as the API provider has any blame. However, security is all about reducing surface area and risk. Treat your customer data as if it’s your own and help them by adding guards that prevent accidental key exposure.
The easiest way to prevent key exposure is by leveraging two tokens rather than one. A refresh token is stored as an environment variable and can only be used to generate short lived access tokens. Unlike the refresh token, these short lived tokens can access the resources, but are time limited such as in hours or days.
The customer will store the refresh token with other API keys. Then your SDK will generate access tokens on SDK init or when the last access token expires. If a CURL command gets pasted into a GitHub issue, then a hacker would need to use it within hours reducing the attack vector (unless it was the actual refresh token which is low probability)
APIs open up entirely new business models where customers can access your API platform programmatically. However, this can make DDoS protection tricky. Most DDoS protection is designed to absorb and reject a large number of requests from bad actors during DDoS attacks but still need to let the good ones through. This requires fingerprinting the HTTP requests to check against what looks like bot traffic. This is much harder for API products as all traffic looks like bot traffic and is not coming from a browser where things like cookies are present.
The magical part about APIs is almost every access requires an API Key. If a request doesn’t have an API key, you can automatically reject it which is lightweight on your servers (Ensure authentication is short circuited very early before later middleware like request JSON parsing). So then how do you handle authenticated requests? The easiest is to leverage rate limit counters for each API key such as to handle X requests per minute and reject those above the threshold with a 429 HTTP response.
There are a variety of algorithms to do this such as leaky bucket and fixed window counters.
APIs are no different than web servers when it comes to good server hygiene. Data can be leaked due to misconfigured SSL certificate or allowing non-HTTPS traffic. For modern applications, there is very little reason to accept non-HTTPS requests, but a customer could mistakenly issue a non HTTP request from their application or CURL exposing the API key. APIs do not have the protection of a browser so things like HSTS or redirect to HTTPS offer no protection.
Test your SSL implementation over at Qualys SSL Test or similar tool. You should also block all non-HTTP requests which can be done within your load balancer. You should also remove any HTTP headers scrub any error messages that leak implementation details. If your API is used only by your own apps or can only be accessed server-side, then review Authoritative guide to Cross-Origin Resource Sharing for REST APIs
APIs provide access to dynamic data that’s scoped to each API key. Any caching implementation should have the ability to scope to an API key to prevent cross-pollution. Even if you don’t cache anything in your infrastructure, you could expose your customers to security holes. If a customer with a proxy server was using multiple API keys such as one for development and one for production, then they could see cross-pollinated data.
#api management #api security #api best practices #api providers #security analytics #api management policies #api access tokens #api access #api security risks #api access keys
1660168020
Neste tutorial, você aprenderá a desenvolver uma API assíncrona com FastAPI e MongoDB . Usaremos o pacote Motor para interagir com o MongoDB de forma assíncrona.
Ao final deste tutorial, você será capaz de:
Comece criando uma nova pasta para armazenar seu projeto chamada "fastapi-mongo":
$ mkdir fastapi-mongo
$ cd fastapi-mongo
Em seguida, crie e ative um ambiente virtual:
$ python3.9 -m venv venv
$ source venv/bin/activate
$ export PYTHONPATH=$PWD
Sinta-se à vontade para trocar virtualenv e Pip por Poetry ou Pipenv . Para saber mais, revise Ambientes Python Modernos .
Em seguida, crie os seguintes arquivos e pastas:
├── app
│ ├── __init__.py
│ ├── main.py
│ └── server
│ ├── app.py
│ ├── database.py
│ ├── models
│ └── routes
└── requirements.txt
Adicione as seguintes dependências ao seu arquivo requirements.txt :
fastapi==0.73.0
uvicorn==0.17.4
Instale-os:
(venv)$ pip install -r requirements.txt
No arquivo app/main.py , defina um ponto de entrada para executar o aplicativo:
import uvicorn
if __name__ == "__main__":
uvicorn.run("server.app:app", host="0.0.0.0", port=8000, reload=True)
Aqui, instruímos o arquivo para executar um servidor Uvicorn na porta 8000 e recarregar a cada alteração de arquivo.
Antes de iniciar o servidor por meio do arquivo de ponto de entrada, crie uma rota base em app/server/app.py :
from fastapi import FastAPI
app = FastAPI()
@app.get("/", tags=["Root"])
async def read_root():
return {"message": "Welcome to this fantastic app!"}
Tags são identificadores usados para agrupar rotas. As rotas com as mesmas tags são agrupadas em uma seção na documentação da API.
Execute o arquivo de ponto de entrada do seu console:
(venv)$ python app/main.py
Navegue até http://localhost:8000 em seu navegador. Você deveria ver:
{
"message": "Welcome to this fantastic app!"
}
Você também pode visualizar a documentação interativa da API em http://localhost:8000/docs :
Construiremos um aplicativo simples para armazenar dados de alunos com as seguintes rotas CRUD:
Antes de mergulharmos na escrita das rotas, vamos primeiro definir o esquema relevante e configurar o MongoDB.
Vamos definir o Schema para o qual nossos dados serão baseados, que representará como os dados são armazenados no banco de dados MongoDB.
Os esquemas Pydantic são usados para validar dados juntamente com serialização (JSON -> Python) e desserialização (Python -> JSON). Ele não serve como um validador de esquema do Mongo , em outras palavras.
Na pasta "app/server/models", crie um novo arquivo chamado student.py :
from typing import Optional
from pydantic import BaseModel, EmailStr, Field
class StudentSchema(BaseModel):
fullname: str = Field(...)
email: EmailStr = Field(...)
course_of_study: str = Field(...)
year: int = Field(..., gt=0, lt=9)
gpa: float = Field(..., le=4.0)
class Config:
schema_extra = {
"example": {
"fullname": "John Doe",
"email": "jdoe@x.edu.ng",
"course_of_study": "Water resources engineering",
"year": 2,
"gpa": "3.0",
}
}
class UpdateStudentModel(BaseModel):
fullname: Optional[str]
email: Optional[EmailStr]
course_of_study: Optional[str]
year: Optional[int]
gpa: Optional[float]
class Config:
schema_extra = {
"example": {
"fullname": "John Doe",
"email": "jdoe@x.edu.ng",
"course_of_study": "Water resources and environmental engineering",
"year": 4,
"gpa": "4.0",
}
}
def ResponseModel(data, message):
return {
"data": [data],
"code": 200,
"message": message,
}
def ErrorResponseModel(error, code, message):
return {"error": error, "code": code, "message": message}
No código acima, definimos um Pydantic Schema chamado StudentSchema
que representa como os dados do aluno serão armazenados em seu banco de dados MongoDB.
Em Pydantic, as reticências , ...
, indicam que um campo é obrigatório. Ele pode ser substituído por None
ou por um valor padrão. Em StudentSchema
, cada campo tem reticências, pois cada campo é importante e o programa não deve prosseguir sem ter os valores configurados.
No campo gpa
e do , adicionamos os validadores , , e :yearStudentSchema
gtltle
gt
e lt
no year
campo garante que o valor passado seja maior que 0 e menor que 9 . Como resultado, valores como 0 , 10 , 11 , resultarão em erros.le
validador no gpa
campo garante que o valor passado seja menor ou igual a 4.0 .Esse esquema ajudará os usuários a enviar solicitações HTTP com a forma adequada para a API -- ou seja, o tipo de dados a serem enviados e como enviá-los.
FastAPI usa Pyantic Schemas para documentar automaticamente modelos de dados em conjunto com Json Schema . A interface do usuário do Swagger renderiza os dados dos modelos de dados gerados. Você pode ler mais sobre como o FastAPI gera a documentação da API aqui .
Como usamos EmailStr
o , precisamos instalar o email-validator .
Adicione-o ao arquivo de requisitos:
pydantic[email]
Instalar:
(venv)$ pip install -r requirements.txt
Com o esquema em vigor, vamos configurar o MongoDB antes de escrever as rotas para a API.
Nesta seção, conectaremos o MongoDB e configuraremos nosso aplicativo para se comunicar com ele.
De acordo com a Wikipedia , o MongoDB é um programa de banco de dados orientado a documentos multiplataforma. Classificado como um programa de banco de dados NoSQL, o MongoDB usa documentos semelhantes a JSON com esquemas opcionais.
Se você não tiver o MongoDB instalado em sua máquina, consulte o guia de instalação da documentação. Uma vez instalado, continue com o guia para executar o processo do mongod daemon. Uma vez feito, você pode verificar se o MongoDB está funcionando, conectando-se à instância por meio do mongo
comando shell:
$ mongo
Para referência, este tutorial usa o MongoDB Community Edition v5.0.6.
$ mongo --version
MongoDB shell version v5.0.6
Build Info: {
"version": "5.0.6",
"gitVersion": "212a8dbb47f07427dae194a9c75baec1d81d9259",
"modules": [],
"allocator": "system",
"environment": {
"distarch": "x86_64",
"target_arch": "x86_64"
}
}
Em seguida, vamos configurar o Motor , um driver assíncrono do MongoDB, para interagir com o banco de dados.
Comece adicionando a dependência ao arquivo de requisitos:
motor==2.5.1
Instalar:
(venv)$ pip install -r requirements.txt
De volta ao aplicativo, adicione as informações de conexão do banco de dados a app/server/database.py :
import motor.motor_asyncio
MONGO_DETAILS = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_DETAILS)
database = client.students
student_collection = database.get_collection("students_collection")
No código acima, importamos Motor, definimos os detalhes da conexão e criamos um cliente via AsyncIOMotorClient .
Em seguida, referenciamos um banco de dados chamado students
e uma coleção (semelhante a uma tabela em um banco de dados relacional) chamada students_collection
. Como essas são apenas referências e não E/S reais, nenhuma delas requer uma await
expressão. Quando a primeira operação de E/S for feita, o banco de dados e a coleção serão criados, caso ainda não existam.
Em seguida, crie uma função auxiliar rápida para analisar os resultados de uma consulta de banco de dados em um dict do Python.
Adicione isso ao arquivo database.py também:
import motor.motor_asyncio
MONGO_DETAILS = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_DETAILS)
database = client.students
student_collection = database.get_collection("students_collection")
# helpers
def student_helper(student) -> dict:
return {
"id": str(student["_id"]),
"fullname": student["fullname"],
"email": student["email"],
"course_of_study": student["course_of_study"],
"year": student["year"],
"GPA": student["gpa"],
}
Em seguida, vamos escrever as operações do banco de dados CRUD.
Comece importando o ObjectId
método do pacote bson na parte superior do arquivo database.py :
from bson.objectid import ObjectId
bson vem instalado como uma dependência do motor.
Em seguida, adicione cada uma das seguintes funções para as operações CRUD:
# Retrieve all students present in the database
async def retrieve_students():
students = []
async for student in student_collection.find():
students.append(student_helper(student))
return students
# Add a new student into to the database
async def add_student(student_data: dict) -> dict:
student = await student_collection.insert_one(student_data)
new_student = await student_collection.find_one({"_id": student.inserted_id})
return student_helper(new_student)
# Retrieve a student with a matching ID
async def retrieve_student(id: str) -> dict:
student = await student_collection.find_one({"_id": ObjectId(id)})
if student:
return student_helper(student)
# Update a student with a matching ID
async def update_student(id: str, data: dict):
# Return false if an empty request body is sent.
if len(data) < 1:
return False
student = await student_collection.find_one({"_id": ObjectId(id)})
if student:
updated_student = await student_collection.update_one(
{"_id": ObjectId(id)}, {"$set": data}
)
if updated_student:
return True
return False
# Delete a student from the database
async def delete_student(id: str):
student = await student_collection.find_one({"_id": ObjectId(id)})
if student:
await student_collection.delete_one({"_id": ObjectId(id)})
return True
No código acima, definimos as operações assíncronas para criar, ler, atualizar e excluir os dados do aluno no banco de dados via motor.
Nas operações de atualização e exclusão, o aluno é procurado no banco de dados para decidir se realiza ou não a operação. Os valores de retorno orientam como enviar respostas ao usuário em que trabalharemos na próxima seção.
Nesta seção, adicionaremos as rotas para complementar as operações do banco de dados no arquivo de banco de dados.
Na pasta "routes", crie um novo arquivo chamado student.py e adicione o seguinte conteúdo a ele:
from fastapi import APIRouter, Body
from fastapi.encoders import jsonable_encoder
from app.server.database import (
add_student,
delete_student,
retrieve_student,
retrieve_students,
update_student,
)
from app.server.models.student import (
ErrorResponseModel,
ResponseModel,
StudentSchema,
UpdateStudentModel,
)
router = APIRouter()
Usaremos o codificador compatível com JSON da FastAPI para converter nossos modelos em um formato compatível com JSON.
Em seguida, conecte a rota do aluno em app/server/app.py :
from fastapi import FastAPI
from app.server.routes.student import router as StudentRouter
app = FastAPI()
app.include_router(StudentRouter, tags=["Student"], prefix="/student")
@app.get("/", tags=["Root"])
async def read_root():
return {"message": "Welcome to this fantastic app!"}
De volta ao arquivo de rotas, adicione o seguinte manipulador para criar um novo aluno:
@router.post("/", response_description="Student data added into the database")
async def add_student_data(student: StudentSchema = Body(...)):
student = jsonable_encoder(student)
new_student = await add_student(student)
return ResponseModel(new_student, "Student added successfully.")
Portanto, a rota espera uma carga útil que corresponda ao formato de StudentSchema
. Exemplo:
{
"fullname": "John Doe",
"email": "jdoe@x.edu.ng",
"course_of_study": "Water resources engineering",
"year": 2,
"gpa": "3.0",
}
Inicie o servidor Uvicorn:
(venv)$ python app/main.py
E atualize a página de documentação da API interativa em http://localhost:8000/docs para ver a nova rota:
Faça o teste também:
Portanto, quando uma solicitação é enviada ao endpoint, ele armazena um corpo de solicitação codificado em JSON na variável student
antes de chamar o add_student
método de banco de dados e armazenar a resposta na new_student
variável. A resposta do banco de dados é então retornada por meio do arquivo ResponseModel
.
Teste também os validadores:
Seguindo em frente, adicione as seguintes rotas para recuperar todos os alunos e um único aluno:
@router.get("/", response_description="Students retrieved")
async def get_students():
students = await retrieve_students()
if students:
return ResponseModel(students, "Students data retrieved successfully")
return ResponseModel(students, "Empty list returned")
@router.get("/{id}", response_description="Student data retrieved")
async def get_student_data(id):
student = await retrieve_student(id)
if student:
return ResponseModel(student, "Student data retrieved successfully")
return ErrorResponseModel("An error occurred.", 404, "Student doesn't exist.")
O que acontece se você não passar um ObjectId válido -- por exemplo,
1
-- para o ID para recuperar uma única rota de aluno? Como você pode lidar melhor com isso no aplicativo?
Quando a operação de exclusão for implementada, você terá a oportunidade de testar a resposta para um banco de dados vazio.
Em seguida, escreva a rota individual para atualizar os dados do aluno:
@router.put("/{id}")
async def update_student_data(id: str, req: UpdateStudentModel = Body(...)):
req = {k: v for k, v in req.dict().items() if v is not None}
updated_student = await update_student(id, req)
if updated_student:
return ResponseModel(
"Student with ID: {} name update is successful".format(id),
"Student name updated successfully",
)
return ErrorResponseModel(
"An error occurred",
404,
"There was an error updating the student data.",
)
Por fim, adicione a rota de exclusão:
@router.delete("/{id}", response_description="Student data deleted from the database")
async def delete_student_data(id: str):
deleted_student = await delete_student(id)
if deleted_student:
return ResponseModel(
"Student with ID: {} removed".format(id), "Student deleted successfully"
)
return ErrorResponseModel(
"An error occurred", 404, "Student with id {0} doesn't exist".format(id)
)
Recupere o ID do usuário que você criou anteriormente e teste a rota de exclusão:
Remova todos os alunos restantes e teste as rotas de leitura novamente, garantindo que as respostas sejam apropriadas para um banco de dados vazio.
Nesta seção, implantaremos o aplicativo no Heroku e configuraremos um banco de dados em nuvem para o MongoDB.
Antes de implantar, precisamos configurar o MongoDB Atlas , um serviço de banco de dados em nuvem para o MongoDB hospedar nosso banco de dados.
Siga o guia de introdução onde você criará uma conta, implantará um cluster de camada gratuita, configurará um usuário e colocará um endereço IP na lista de permissões.
Para fins de teste, use
0.0.0.0/0
para o IP da lista de permissões para permitir o acesso de qualquer lugar. Para um aplicativo de produção, você desejará restringir o acesso a um IP estático.
Uma vez feito, pegue as informações de conexão do banco de dados do seu cluster clicando no botão "Conectar":
Clique na segunda opção, "Conectar ao seu aplicativo":
Copie o URL de conexão, certificando-se de atualizar a senha. Defina o banco de dados padrão para "alunos" também. Será semelhante a:
mongodb+srv://foobar:foobar@cluster0.0reol.mongodb.net/students?retryWrites=true&w=majority
Em vez de codificar esse valor em nosso aplicativo, vamos defini-lo como uma variável de ambiente. Crie um novo arquivo chamado .env na raiz do projeto e as informações de conexão para ele:
MONGO_DETAILS=your_connection_url
Certifique-se de substituir
your_connection_url
pelo URL copiado.
Em seguida, para simplificar o gerenciamento de variáveis de ambiente em nosso aplicativo, vamos instalar o pacote Python Decouple . Adicione-o ao seu arquivo de requisitos assim:
python-decouple==3.6
Instalar:
(venv)$ pip install -r requirements.txt
No arquivo app/server/database.py , importe a biblioteca:
from decouple import config
O método importado config
varre o diretório raiz em busca de um arquivo .env e lê o conteúdo passado para ele. Então, no nosso caso, ele vai ler a MONGO_DETAILS
variável.
Em seguida, altere a MONGO_DETAILS
variável para:
MONGO_DETAILS = config("MONGO_DETAILS") # read environment variable
Antes de implantar, vamos testar o aplicativo localmente com o banco de dados em nuvem para garantir que a conexão esteja configurada corretamente. Reinicie seu servidor Uvicorn e teste cada rota da documentação interativa em http://localhost:8000/docs .
Você deve conseguir ver os dados no painel do Atlas:
Por fim, vamos implantar o aplicativo no Heroku .
Heroku é uma plataforma de nuvem como serviço (PaaS) usada para implantar e dimensionar aplicativos.
Se necessário, inscreva-se em uma conta Heroku e instale o Heroku CLI .
Antes de continuar, crie um arquivo .gitignore no projeto para evitar o check-in da pasta "venv" e do arquivo .env no git:
(venv)$ touch .gitignore
Adicione o seguinte:
.env
venv/
__pycache__
Em seguida, adicione um Procfile à raiz do seu projeto:
web: uvicorn app.server.app:app --host 0.0.0.0 --port=$PORT
Notas:
web
junto com o comando para servir o Uvicorn.$PORT
variável de ambiente.Seu projeto agora deve ter os seguintes arquivos e pastas:
├── .env
├── .gitignore
├── Procfile
├── app
│ ├── __init__.py
│ ├── main.py
│ └── server
│ ├── app.py
│ ├── database.py
│ ├── models
│ │ └── student.py
│ └── routes
│ └── student.py
└── requirements.txt
Na raiz do seu projeto, inicialize um novo repositório git:
(venv)$ git init
(venv)$ git add .
(venv)$ git commit -m "My fastapi and mongo application"
Agora, podemos criar um novo aplicativo no Heroku:
(venv)$ heroku create
Além de criar um novo aplicativo, esse comando cria um repositório git remoto no Heroku para enviarmos nosso aplicativo para implantação. Em seguida, ele define isso como um controle remoto no repositório local automaticamente para nós.
Você pode verificar se o controle remoto está configurado executando
git remote -v
.
Anote o URL do seu aplicativo.
Como não adicionamos o arquivo .env ao git, precisamos definir a variável de ambiente no ambiente Heroku:
(venv)$ heroku config:set MONGO_DETAILS="your_mongo_connection_url"
Novamente, certifique-se de substituir
your_connection_url
pelo URL de conexão real.
Envie seu código para o Heroku e certifique-se de que pelo menos uma instância do aplicativo esteja em execução:
(venv)$ git push heroku master
(venv)$ heroku ps:scale web=1
Execute heroku open
para abrir seu aplicativo em seu navegador padrão.
Você implantou com sucesso seu aplicativo no Heroku. Teste-o.
Neste tutorial, você aprendeu como criar um aplicativo CRUD com FastAPI e MongoDB e implantá-lo no Heroku. Faça uma autoverificação rápida revisando os objetivos no início do tutorial. Você pode encontrar o código usado neste tutorial no GitHub .
Fonte: https://testdrive.io
1601381326
We’ve conducted some initial research into the public APIs of the ASX100 because we regularly have conversations about what others are doing with their APIs and what best practices look like. Being able to point to good local examples and explain what is happening in Australia is a key part of this conversation.
The method used for this initial research was to obtain a list of the ASX100 (as of 18 September 2020). Then work through each company looking at the following:
With regards to how the APIs are shared:
#api #api-development #api-analytics #apis #api-integration #api-testing #api-security #api-gateway
1604399880
I’ve been working with Restful APIs for some time now and one thing that I love to do is to talk about APIs.
So, today I will show you how to build an API using the API-First approach and Design First with OpenAPI Specification.
First thing first, if you don’t know what’s an API-First approach means, it would be nice you stop reading this and check the blog post that I wrote to the Farfetchs blog where I explain everything that you need to know to start an API using API-First.
Before you get your hands dirty, let’s prepare the ground and understand the use case that will be developed.
If you desire to reproduce the examples that will be shown here, you will need some of those items below.
To keep easy to understand, let’s use the Todo List App, it is a very common concept beyond the software development community.
#api #rest-api #openai #api-first-development #api-design #apis #restful-apis #restful-api