Neil  Morgan

Neil Morgan

1660168020

Como Desenvolver Uma API Assíncrona Com FastAPI E MongoDB

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.

Objetivos

Ao final deste tutorial, você será capaz de:

  1. Desenvolva uma API RESTful com Python e FastAPI
  2. Interagir com o MongoDB de forma assíncrona
  3. Execute o MongoDB na nuvem com o MongoDB Atlas
  4. Implantar um aplicativo FastAPI no Heroku

Configuração inicial

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 :

fastapi swagger ui

Rotas

Construiremos um aplicativo simples para armazenar dados de alunos com as seguintes rotas CRUD:

rotas cruas

Antes de mergulharmos na escrita das rotas, vamos primeiro definir o esquema relevante e configurar o MongoDB.

Esquema

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 StudentSchemaque 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 Noneou 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 gpae do , adicionamos os validadores , , e :yearStudentSchema gtltle

  1. gte ltno yearcampo garante que o valor passado seja maior que 0 e menor que 9 . Como resultado, valores como 0 , 10 , 11 , resultarão em erros.
  2. levalidador no gpacampo 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 EmailStro , 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.

MongoDB

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.

Configuração do MongoDB

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 mongocomando 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"
    }
}

Configuração do motor

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 studentse 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 awaitexpressã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.

Operações CRUD do banco de dados

Comece importando o ObjectIdmé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.

Rotas CRUD

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!"}

Crio

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:

swagger ui

Faça o teste também:

swagger ui

Portanto, quando uma solicitação é enviada ao endpoint, ele armazena um corpo de solicitação codificado em JSON na variável studentantes de chamar o add_studentmétodo de banco de dados e armazenar a resposta na new_studentvariável. A resposta do banco de dados é então retornada por meio do arquivo ResponseModel.

Teste também os validadores:

  1. O ano deve ser maior que 0 e menor que 10
  2. O GPA deve ser menor ou igual a 4,0

swagger ui

Ler

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.")

swagger ui

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.

Atualizar

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.",
    )

swagger ui

Excluir

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:

swagger ui

Remova todos os alunos restantes e teste as rotas de leitura novamente, garantindo que as respostas sejam apropriadas para um banco de dados vazio.

Implantação

Nesta seção, implantaremos o aplicativo no Heroku e configuraremos um banco de dados em nuvem para o MongoDB.

Atlas 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/0para 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":

atlas mongodb

Clique na segunda opção, "Conectar ao seu aplicativo":

atlas mongodb

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_urlpelo 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 configvarre 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_DETAILSvariável.

Em seguida, altere a MONGO_DETAILSvariável para:

MONGO_DETAILS = config("MONGO_DETAILS")  # read environment variable

Testando localmente

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:

atlas mongodb

Implantando no Heroku

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:

  1. Um Procfile é um arquivo de texto, colocado na raiz do seu projeto, que orienta o Heroku sobre como executar seu aplicativo. Como estamos servindo um aplicativo da web, definimos o tipo de processo webjunto com o comando para servir o Uvicorn.
  2. O Heroku expõe dinamicamente uma porta para que seu aplicativo seja executado no momento da implantação, que é exposta por meio da $PORTvariá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_urlpelo 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 openpara abrir seu aplicativo em seu navegador padrão.

Você implantou com sucesso seu aplicativo no Heroku. Teste-o.

Conclusã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 

What is GEEK

Buddha Community

Como Desenvolver Uma API Assíncrona Com FastAPI E MongoDB

Como Desenvolver Uma API Assíncrona Com FastAPI E MongoDB

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.

Objetivos

Ao final deste tutorial, você será capaz de:

  1. Explique o que é o Beanie ODM e por que você pode querer usá-lo
  2. Interaja com o MongoDB de forma assíncrona usando o Beanie ODM
  3. Desenvolva uma API RESTful com Python e FastAPI

Por que Beanie ODM?

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 .

Configuração inicial

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!"
}

O que estamos construindo?

Construiremos um aplicativo de análise de produtos que nos permitirá realizar as seguintes operações:

  • Criar avaliações
  • Ler comentários
  • Atualizar comentários
  • Excluir comentários

Antes de mergulhar na escrita das rotas, vamos usar o Beanie para configurar o modelo de banco de dados para nosso aplicativo.

Esquema de banco de dados

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 Documentclasse de Beanie. A Documentclasse é 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 Settingsclasse 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 Documentclasse é 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 Configsubclasse 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 ProductReviewque 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 UpdateProductReviewclasse 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.

MongoDB

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.

Configuração do MongoDB

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 mongocomando 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"
    }
}

Configurando o banco de dados

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_beaniemétodo recebe dois argumentos:

  1. database- O nome do banco de dados a ser usado.
  2. document_models- Uma lista de modelos de documentos definidos -- o ProductReviewmodelo, no nosso caso.

A init_dbfunçã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.

Rotas

Nesta seção, construiremos as rotas para realizar operações CRUD em seu banco de dados a partir do aplicativo:

  1. PUBLICAR revisão
  2. RECEBA uma única revisão e RECEBA todas as revisões
  3. PUT única revisão
  4. DELETE revisão única

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 APIRouterclasse 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!"}

Crio

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"
}

Ler

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:

  1. Na primeira função, a função recebe um ID do tipo ObjectiD, a codificação padrão para IDs do MongoDB. O registro é recuperado usando o método get() .
  2. Na segunda, recuperamos todas as avaliações usando o método find_all() . O 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"
}

Atualizar

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"
}

Excluir

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.

Conclusão

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

#crud #fastapi #mongodb #api 

Top 10 API Security Threats Every API Team Should Know

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.

Insecure pagination and resource limits

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:

  1. For data APIs, legitimate customers may need to fetch and sync a large number of records such as via cron jobs. Artificially small pagination limits can force your API to be very chatty decreasing overall throughput. Max limits are to ensure memory and scalability requirements are met (and prevent certain DDoS attacks), not to guarantee security.
  2. This offers zero protection to a hacker that writes a simple script that sleeps a random delay between repeated accesses.
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

How to secure against pagination attacks

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.

Insecure API key generation

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.

How to secure against API key pools

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.

Accidental key exposure

APIs are used in a way that increases the probability credentials are leaked:

  1. APIs are expected to be accessed over indefinite time periods, which increases the probability that a hacker obtains a valid API key that’s not expired. You save that API key in a server environment variable and forget about it. This is a drastic contrast to a user logging into an interactive website where the session expires after a short duration.
  2. The consumer of an API has direct access to the credentials such as when debugging via Postman or CURL. It only takes a single developer to accidently copy/pastes the CURL command containing the API key into a public forum like in GitHub Issues or Stack Overflow.
  3. API keys are usually bearer tokens without requiring any other identifying information. APIs cannot leverage things like one-time use tokens or 2-factor authentication.

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.

How to 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)

Exposure to DDoS attacks

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.

Stopping DDoS attacks

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.

Incorrect server security

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.

How to ensure proper SSL

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

Incorrect caching headers

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

Neil  Morgan

Neil Morgan

1660168020

Como Desenvolver Uma API Assíncrona Com FastAPI E MongoDB

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.

Objetivos

Ao final deste tutorial, você será capaz de:

  1. Desenvolva uma API RESTful com Python e FastAPI
  2. Interagir com o MongoDB de forma assíncrona
  3. Execute o MongoDB na nuvem com o MongoDB Atlas
  4. Implantar um aplicativo FastAPI no Heroku

Configuração inicial

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 :

fastapi swagger ui

Rotas

Construiremos um aplicativo simples para armazenar dados de alunos com as seguintes rotas CRUD:

rotas cruas

Antes de mergulharmos na escrita das rotas, vamos primeiro definir o esquema relevante e configurar o MongoDB.

Esquema

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 StudentSchemaque 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 Noneou 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 gpae do , adicionamos os validadores , , e :yearStudentSchema gtltle

  1. gte ltno yearcampo garante que o valor passado seja maior que 0 e menor que 9 . Como resultado, valores como 0 , 10 , 11 , resultarão em erros.
  2. levalidador no gpacampo 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 EmailStro , 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.

MongoDB

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.

Configuração do MongoDB

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 mongocomando 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"
    }
}

Configuração do motor

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 studentse 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 awaitexpressã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.

Operações CRUD do banco de dados

Comece importando o ObjectIdmé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.

Rotas CRUD

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!"}

Crio

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:

swagger ui

Faça o teste também:

swagger ui

Portanto, quando uma solicitação é enviada ao endpoint, ele armazena um corpo de solicitação codificado em JSON na variável studentantes de chamar o add_studentmétodo de banco de dados e armazenar a resposta na new_studentvariável. A resposta do banco de dados é então retornada por meio do arquivo ResponseModel.

Teste também os validadores:

  1. O ano deve ser maior que 0 e menor que 10
  2. O GPA deve ser menor ou igual a 4,0

swagger ui

Ler

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.")

swagger ui

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.

Atualizar

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.",
    )

swagger ui

Excluir

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:

swagger ui

Remova todos os alunos restantes e teste as rotas de leitura novamente, garantindo que as respostas sejam apropriadas para um banco de dados vazio.

Implantação

Nesta seção, implantaremos o aplicativo no Heroku e configuraremos um banco de dados em nuvem para o MongoDB.

Atlas 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/0para 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":

atlas mongodb

Clique na segunda opção, "Conectar ao seu aplicativo":

atlas mongodb

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_urlpelo 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 configvarre 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_DETAILSvariável.

Em seguida, altere a MONGO_DETAILSvariável para:

MONGO_DETAILS = config("MONGO_DETAILS")  # read environment variable

Testando localmente

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:

atlas mongodb

Implantando no Heroku

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:

  1. Um Procfile é um arquivo de texto, colocado na raiz do seu projeto, que orienta o Heroku sobre como executar seu aplicativo. Como estamos servindo um aplicativo da web, definimos o tipo de processo webjunto com o comando para servir o Uvicorn.
  2. O Heroku expõe dinamicamente uma porta para que seu aplicativo seja executado no momento da implantação, que é exposta por meio da $PORTvariá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_urlpelo 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 openpara abrir seu aplicativo em seu navegador padrão.

Você implantou com sucesso seu aplicativo no Heroku. Teste-o.

Conclusã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 

Autumn  Blick

Autumn Blick

1601381326

Public ASX100 APIs: The Essential List

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.

Method

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:

  1. Whether the company had a public API: this was found by googling “[company name] API” and “[company name] API developer” and “[company name] developer portal”. Sometimes the company’s website was navigated or searched.
  2. Some data points about the API were noted, such as the URL of the portal/documentation and the method they used to publish the API (portal, documentation, web page).
  3. Observations were recorded that piqued the interest of the researchers (you will find these below).
  4. Other notes were made to support future research.
  5. You will find a summary of the data in the infographic below.

Data

With regards to how the APIs are shared:

#api #api-development #api-analytics #apis #api-integration #api-testing #api-security #api-gateway

An API-First Approach For Designing Restful APIs | Hacker Noon

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.

Preparing the ground

Before you get your hands dirty, let’s prepare the ground and understand the use case that will be developed.

Tools

If you desire to reproduce the examples that will be shown here, you will need some of those items below.

  • NodeJS
  • OpenAPI Specification
  • Text Editor (I’ll use VSCode)
  • Command Line

Use Case

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