Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Learn why MongoDB was selected as a leader in the 2024 Gartner® Magic Quadrant™
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Pythonchevron-right

Crie uma Cocktail API com o Beanie e o MongoDB

Mark Smith6 min read • Published May 09, 2022 • Updated Oct 01, 2024
FastAPIAtlasPesquisaPython
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Tenho uma coleção do MongoDB contendo receitas de coquetéis que criei durante a pandemia.
Recentemente, estou tentando construir uma API sobre ela usando algumas tecnologias que conheço bem. Não estava muito satisfeito com os resultados. Escrever código para transformar o BSON que sai do MongoDB em JSON adequado é relativamente trabalhoso. Eu queria algo mais declarativo, mas a minha tentativa mais recente – uma mash-up de Flask, MongoEngine e Marshmallow– parecia desajeitada e repetitiva. Eu estava prestes a começar a experimentar a construção de minha própria estrutura declarativa, quando me deparei com uma introdução a um novo MongoDB ODM chamado Beanie. Parecia exatamente o que eu estava procurando.
O código usado nesta publicação se baseia em grande parte na postagem de Beanie com link acima. Eu o personalizei de acordo com minhas necessidades e adicionei um endpoint extra que usa o MongoDB Atlas Search para fornecer preenchimento automático para uma GUI que estou planejando criar no futuro.
Você pode encontrar todo o código no GitHub.
Observação: o código aqui foi escrito para oBeanie 1.26.0. Confira o registro de alterações doGorro
Tenho uma coleção de documentos que se parece um pouco com isto:
1{
2 "_id": "5f7daa158ec9dfb536781b0a",
3 "name": "Hunter's Moon",
4 "ingredients": [
5 {
6 "name": "Vermouth",
7 "quantity": {
8 "quantity": "25",
9 "unit": "ml"
10 }
11 },
12 {
13 "name": "Maraschino Cherry",
14 "quantity": {
15 "quantity": "15",
16 "unit": "ml"
17 }
18 },
19 {
20 "name": "Sugar Syrup",
21 "quantity": {
22 "quantity": "10",
23 "unit": "ml"
24 }
25 },
26 {
27 "name": "Lemonade",
28 "quantity": {
29 "quantity": "100",
30 "unit": "ml"
31 }
32 },
33 {
34 "name": "Blackberries",
35 "quantity": {
36 "quantity": "2",
37 "unit": null
38 }
39 }
40 ]
41}
A promessa do Beanie e da FastAPI – apenas para criar um modelo para esses dados e fazer com que ele traduza automaticamente os tipos de campos complicados, como ObjectId e Date entre a representação BSON e JSON – era muito tentadora, então eu chamei um novo projeto Python e defini meu esquema em um submódulo de modelos da seguinte forma:
1class Cocktail(Document):
2 class Settings:
3 name = "recipes"
4
5 name: str
6 ingredients: List["Ingredient"]
7 instructions: List[str]
8
9
10class Ingredient(BaseModel):
11 name: str
12 quantity: Optional["IngredientQuantity"]
13
14
15class IngredientQuantity(BaseModel):
16 quantity: Optional[str]
17 unit: Optional[str]
18
19
20class IngredientAggregation(BaseModel):
21 """ A model for an ingredient count. """
22
23 id: str = Field(None, alias="_id")
24 total: int
Fiquei feliz em ver que eu poderia definir uma classe interna Settings e substituir o nome da coleção. Era um recurso que eu achava que deveria estar lá, mas não tinha certeza se estaria.
A outra coisa que foi um pouco trabalhosa foi fazer Cocktail se referir a Ingredient, que não estava definido até então. Felizmente, posso apenas usar o nome da turma em uma string e as coisas serão coladas mais tarde.
O pacotebeanieCocktails, definido no arquivo__init__.py, contém principalmente código para inicializar FastAPI, Motor eBeanie:
1# ... some code skipped
2
3async def app_lifespan(app: FastAPI):
4 # startup code goes here:
5 client: AsyncIOMotorClient = AsyncIOMotorClient(
6 Settings().mongodb_url,
7 )
8 await init_beanie(client.get_default_database(), document_models=[Cocktail])
9 app.include_router(cocktail_router, prefix="/v1")
10
11 yield
12
13 # shutdown code goes here:
14 client.close()
15
16app = FastAPI(lifespan=app_lifespan)
17
18# I'm using pydantic_settings to load the MongoDB connection string from an environment variable,
19# or it defaults to a local installation.
20class Settings(BaseSettings):
21 mongodb_url: str = "mongodb://localhost:27017/cocktails"
O código acima define um manipulador de vida útil para a inicialização do aplicativo FastAPI. Ele se conecta ao MongoDB, configura o Goanie com a conexão do banco de dados e fornece o modeloCocktail que usarei para o Goanie.
A última linha adiciona o cocktail_router ao FastAPI. É um APIRouter definido no submódulo derotas .
Agora é hora de mostrar o arquivo de rotas. Foi nele em que passei a maior parte do meu tempo. Fiquei impressionado com a rapidez com que consegui desenvolver os pontos de extremidade da API.
1# ... imports skipped
2
3cocktail_router = APIRouter()
O cocktail_router é responsável por rotear caminhos de URL para diferentes manipuladores de função que fornecerão dados a serem renderizados como JSON. Provavelmente, o manipulador mais simples é:
1@cocktail_router.get("/cocktails/", response_model=List[Cocktail])
2async def list_cocktails():
3 return await Cocktail.find_all().to_list()
Esse manipulador tira o máximo proveito desses fatos: a FastAPI renderizará automaticamente as instâncias do Pydantic como JSON; e os modelos Beanie Document são definidos usando o Pydantic.Cocktail.find_all() retorna um iterador sobre todos os documentos Cocktail na coleção recipes. A FastAPI não pode lidar diretamente com esses iteradores, portanto, a sequência é convertida em uma lista usando o método to_list().
Se você tiver o executor de tarefas Just instalado, poderá executar o servidor com:
1just run
Caso contrário, você pode executá-lo diretamente:
1uvicorn beaniecocktails:app --reload
E então você pode testar o ponto de extremidade apontando seu navegador para "http://localhost:8000/v1/cocktails/".
Eu bebo muitos coquetéis
Eu bebo muitos coquetéis
Um ponto de extremidade semelhante para apenas um único coquetel é perfeitamente encapsulado por dois métodos: um para pesquisar um documento por _id e gerar um erro "404 Não Encontrado" se ele não existir e um manipulador para rotear a solicitação HTTP. Os dois são perfeitamente colados usando a declaração Depends que converte o cocktail_id fornecido em uma instância Cocktail carregada.
1async def get_cocktail(cocktail_id: PydanticObjectId) -> Cocktail:
2 """ Helper function to look up a cocktail by id """
3
4 cocktail = await Cocktail.get(cocktail_id)
5 if cocktail is None:
6 raise HTTPException(status_code=404, detail="Cocktail not found")
7 return cocktail
8
9@cocktail_router.get("/cocktails/{cocktail_id}", response_model=Cocktail)
10async def get_cocktail_by_id(cocktail: Cocktail = Depends(get_cocktail)):
11 return cocktail
Agora, vamos ao que eu realmente gostaria de ver no Beanie: sua integração com a framework de agregação do MongoDB. Os pipelines de agregação podem remodelar documentos por meio de projeção ou agrupamento, e o Beanie permite que os documentos resultantes sejam mapeados para uma subclasse BaseModel Pydantic.
Usando essa técnica, pode ser adicionado um ponto de extremidade que forneça um índice de todos os componentes e o número de coquetéis que cada um aparece em:
1# models.py:
2
3class IngredientAggregation(BaseModel):
4 """ A model for an ingredient count. """
5
6 id: str = Field(None, alias="_id")
7 total: int
8
9# routes.py:
10
11@cocktail_router.get("/ingredients", response_model=List[IngredientAggregation])
12async def list_ingredients():
13 """ Group on each ingredient name and return a list of `IngredientAggregation`s. """
14
15 return await Cocktail.aggregate(
16 aggregation_query=[
17 {"$unwind": "$ingredients"},
18 {"$group": {"_id": "$ingredients.name", "total": {"$sum": 1}}},
19 {"$sort": {"_id": 1}},
20 ],
21 projection_model=IngredientAggregation,
22 ).to_list()
Os resultados, em "http://localhost:8000/v1/ingredients", são mais ou menos assim:
1[
2 {"_id":"7-Up","total":1},
3 {"_id":"Amaretto","total":2},
4 {"_id":"Angostura Bitters","total":1},
5 {"_id":"Apple schnapps","total":1},
6 {"_id":"Applejack","total":1},
7 {"_id":"Apricot brandy","total":1},
8 {"_id":"Bailey","total":1},
9 {"_id":"Baileys irish cream","total":1},
10 {"_id":"Bitters","total":3},
11 {"_id":"Blackberries","total":1},
12 {"_id":"Blended whiskey","total":1},
13 {"_id":"Bourbon","total":1},
14 {"_id":"Bourbon Whiskey","total":1},
15 {"_id":"Brandy","total":7},
16 {"_id":"Butterscotch schnapps","total":1},
17]
Curti tanto esse recurso que decidi usá-lo junto com o MongoDB Atlas Search, que fornece pesquisa de texto gratuita em coleções do MongoDB, para implementar um endpoint de preenchimento automático.
A primeira etapa foi adicionar um índice de pesquisa na coleção recipes, na interface web do MongoDB Atlas:
Crie um novo índice de pesquisa na coleção de receitas
Crie um novo índice de pesquisa na coleção `recipes`
Eu precisei adicionar o campo name como um tipo de campo "autocomplete".
O campo de nome deve ser do tipo "autocomplete"
O campo de nome deve ser do tipo "preenchimento automático"
Esperei que o índice terminasse de ser criado, o que não demorou muito, pois não é uma coleção muito grande. Então eu estava pronto para gravar meu endpoint de preenchimento automático:
1@cocktail_router.get("/cocktail_autocomplete", response_model=List[str])
2async def cocktail_autocomplete(fragment: str):
3 """ Return an array of cocktail names matched from a string fragment. """
4
5 return [
6 c["name"]
7 for c in await Cocktail.aggregate(
8 aggregation_query=[
9 {
10 "$search": {
11 "autocomplete": {
12 "query": fragment,
13 "path": "name",
14 }
15 }
16 }
17 ]
18 ).to_list()
19 ]
O estágio de agregação do $search utiliza especificamente um índice de pesquisa. Neste caso, estou usando o tipo autocomplete, para corresponder ao tipo de índice que criei no campo name. Como eu queria que a resposta fosse a mais leve possível, estou assumindo a serialização para JSON, extraindo o nome de cada instância Cocktail e apenas retornando uma lista de strings.
Os resultados são ótimos!
Abordando meu navegador em http://localhost:8000/v1/Cocktail_autocomplete?fragment=fi me dá ["Imperial Fizz","Vodka Fizz"]e http://localhost:8000/v1/Cocktail_autocomplete?fragment= Ma me dá ["Manhattan","Espresso Martini"].
A próxima etapa é construir um frontend do React, para que eu possa realmente chamar isso de aplicativo FARM Stack.

Resumo

Fiquei realmente impressionado com a rapidez com que consegui colocar tudo isso em funcionamento. O tratamento de instâncias ObjectId era totalmente invisível, graças ao tipo PydanticObjectId do Beanie, e vi outro código de exemplo que mostra como os valores BSON Date são igualmente bem tratados.
Preciso ver como posso criar alguma funcionalidade HATEOAS nos endpoints, com entidades vinculadas a suas URL canônicas. A paginação também é algo que será importante à medida que minha coleção crescer, mas acho que já sei como lidar com isso..
Espero que tenha gostado deste rápido resumo da minha primeira experiência usando o Beanie. Na próxima vez que estiver construindo uma API no MongoDB, recomendo que você experimente!
Se esta foi sua primeira exposição à Estrutura de agregação, eu realmente recomendável que você leia nossa documentação sobre esse recurso poderoso do MongoDB. Ou, se você realmente quiser colocar a mão na massa, por que não conferir nosso cursogratuito de Agregação da MongoDB University?
Se tiver dúvidas, acesse o site da nossa comunidade de desenvolvedores, no qual os engenheiros e a comunidade do MongoDB ajudarão você a desenvolver sua próxima grande ideia com o MongoDB.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Como implementar o Agentic RAG usando o Claude 3.5 Sonnet, LlamaIndex e MongoDB


Jul 02, 2024 | 17 min read
Artigo

Aplicar linguagem natural em consultas do MongoDB com o Google Gemini


Dec 13, 2024 | 8 min read
Artigo

Sistemas multiagentes com AutoGen e MongoDB


Sep 18, 2024 | 10 min read
Início rápido

Melhores práticas para usar Frasco e MongoDB


Sep 16, 2024 | 5 min read
Sumário
  • Resumo