A Voyage AI se une ao MongoDB para impulsionar aplicativos de AI mais precisos e confiáveis no Atlas.

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 .

Crie uma Cocktail API com o Beanie e o MongoDB

Mark Smith6 min read • Published May 09, 2022 • Updated Oct 01, 2024
Facebook Icontwitter iconlinkedin icon
Avaliar este 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, tenho tentado criar uma API com base nele, usando algumas tecnologias que conheço bem. Não fiquei muito feliz com os resultados. Escrever código para transformar o BSON que sai do MongoDB em JSON adequado é relativamente complicado. Eu senti que queria algo mais declarativo, mas minha tentativa mais recente — uma combinação de Flask, MongoEngine e Marshmallow— parecia desleixada e repetitiva. Eu estava prestes a começar a testar a criação do meu próprio framework declarativo e então me deparei com uma introdução a um novo ODM do MongoDB chamado Beanie. Parecia exatamente o que eu estava procurando.
O código usado nesta publicação se baseia muitona publicação do Beanie com link acima. Eu o personalizei de acordo com minhas necessidades e adicionei um endpoint extra que faz uso do MongoDB Atlas Search, para fornecer preenchimento automático, para uma GUI que estou planejando construir no futuro.
Você pode encontrar todo o código no GitHub.
Note: The code here was written for Beanie 1.26.0. Check out the Beanie Changelog
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
I was pleased to see that I could define a Settings inner class and override the collection name. It was a feature that I thought should be there, but wasn't totally sure it would be.
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 .
So now it's time to show you the routes file—this is where I spent most of my time. I was amazed by how quickly I could get API endpoints developed.
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 endpoint apontando seu navegador para "http://localhost:8000/v1/cocktails/".
I drink too many
cocktails
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
Now for the thing that I really like about Beanie: its integration with MongoDB's Framework de agregação. Aggregation pipelines can reshape documents through projection or grouping, and Beanie allows the resulting documents to be mapped to a Pydantic BaseModel subclass.
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]
Gostei tanto desse 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 do Atlas Search na collectionrecipes, na interface web do MongoDB Atlas :
Create a new search index on the recipes collection
Crie um novo índice de pesquisa na coleção `recipes`
Eu precisei adicionar o campo name como um tipo de campo "autocomplete".
The name field must be of type "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 é criar um front-end 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 você tenha gostado dessa rápida apresentação da minha primeira experiência com o Beanie. Da próxima vez que estiver criando uma API sobre o MongoDB, recomendo que 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?
Em caso de dúvidas, acesse nosso website da comunidade de desenvolvedores, no qual os engenheiros do MongoDB e a MongoDB Community ajudarão você a colocar em prática sua próxima grande ideia com o MongoDB.

Facebook Icontwitter iconlinkedin icon
Avaliar este tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

É seguro sair? Investigação de dados com o MongoDB


Sep 23, 2022 | 11 min read
Artigo

Usando o SuperDuperDB para acelerar o desenvolvimento de IA no MongoDB Atlas Vector Search


Sep 18, 2024 | 6 min read
Tutorial

Autenticação do MongoDB Atlas usando contas de serviço (OAuth)


Jan 23, 2025 | 9 min read
Tutorial

Como usar os módulos de incorporações e reclassificação do Cohere com o MongoDB Atlas


Mar 13, 2025 | 10 min read
Sumário