I want to put different files, how to use backlink? #577
-
Say I want to put 'House' and 'Door' in two different files, namely "house.py" and "door.py" respectively. Let's presume main.pyimport asyncio
from beanie import init_beanie
from motor import motor_asyncio
from house import House
from door import Door
async def init():
client = motor_asyncio.AsyncIOMotorClient(
'mongodb://root:12345678@localhost:27017'
)
await init_beanie(
database=client.Noah,
document_models=[
House, Door,
],
)
async def main():
await init()
print(await House.find().to_list())
print(await Door.find().to_list())
if __name__ == '__main__':
asyncio.run(main()) house.pyfrom beanie import Document, Link
class House(Document):
name: str
door: Link["Door"]
class Settings:
name = 'BackLinkHouse' door.pyfrom beanie import Document, BackLink
from pydantic import Field
from house import House
class Door(Document):
height: int = 2
width: int = 1
house: BackLink[House] = Field(original_field="door")
class Settings:
name = 'BackLinkDoor'
If you change door: |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 4 replies
-
I've found that doing this does the trick. main.pyimport asyncio
from beanie import init_beanie
from motor import motor_asyncio
from house import House
from door import Door
House.update_forward_refs(Door=Door)
Door.update_forward_refs(House=House)
async def init():
client = motor_asyncio.AsyncIOMotorClient(
'mongodb://root:12345678@localhost:27017'
)
await init_beanie(
database=client.Noah,
document_models=[
House, Door,
],
)
async def main():
await init()
print(await House.find().to_list())
print(await Door.find().to_list())
if __name__ == '__main__':
asyncio.run(main()) house.pyfrom beanie import Document, Link
class House(Document):
name: str
door: Link['Door']
class Settings:
name = 'BackLinkHouse' door.pyfrom beanie import Document, BackLink
from pydantic import Field
class Door(Document):
height: int = 2
width: int = 1
house: BackLink['House'] = Field(original_field="door")
class Settings:
name = 'BackLinkDoor' Is there any other way? |
Beta Was this translation helpful? Give feedback.
-
Your workaround is nice. You can do things like this also: File a_circular_import.py: from __future__ import annotations
from typing import Optional
from pydantic import BaseModel
class A(BaseModel):
b: Optional[B] = None
from b_circular_import import B
A.update_forward_refs() File b_circular_import.py: from __future__ import annotations
from pydantic import BaseModel
class B(BaseModel):
a: A
from a_circular_import import A
B.update_forward_refs()
b = B(a=A())
print(
b.json(indent=2)
) But I like your solution more |
Beta Was this translation helpful? Give feedback.
-
Hi, I'm trying to solve a similar problem. And it seems that The solution from @roman-right give me this error when I try to access the API documentation: (I'm using FastAPI)
And I'm using I tried some other things for hours, like Here are some examples of my models which give me the problems:
Can someone have an idea how to solve it? Best regards, Diego |
Beta Was this translation helpful? Give feedback.
-
Hi @hgalytoby, I think my problem is related to Pydantic generation of the json schema for the FastAPI OpenAPI documentation (it is what give me the error, the insert operation would work). I made a minimum example bellow (you can try in a unique file and the errors happen same way, but uncomment the imports and you can test with the directory structure): # database/ingredients_models.py
from pydantic import Field
from beanie import Document, BackLink
from typing import List
# from database.drinks_models import Drinks
class Ingredients(Document):
name: str
used_in_drinks: List[BackLink["Drinks"]] = Field(original_field="ingredients")
# database/drinks_models.py
from beanie import Document, Link
from typing import Optional
# from database.ingredients_models import Ingredients
class Drinks(Document):
name: str
ingredients: Optional[List[Link[Ingredients]]] = None
# database/db_config.py
import motor.motor_asyncio
import os
MONGODB_URL = os.getenv("MONGODB_URL", "")
client = motor.motor_asyncio.AsyncIOMotorClient(
"MONGODB_URL", serverSelectionTimeoutMS=5000,
uuidRepresentation="standard"
)
# routers/ingredients_router.py
from fastapi import APIRouter
# from database.ingredients_models import Ingredients
router = APIRouter(
prefix="/ingredients",
tags=["Ingredients"],
responses={404: {"description": "Not Found"}},
)
response_schema = Ingredients
@router.get("/{item_id}", response_model=response_schema)
async def read_item(
item: response_schema,
):
return item # Will return the item from the Depends
# main.py
from fastapi import FastAPI
import os
import uvicorn
from beanie import init_beanie
from contextlib import asynccontextmanager
from fastapi import FastAPI
# from database.db_config import client
# from database.drinks_models import Drinks
# from database.ingredients_models import Ingredients
# from routers import ingredients_router
async def my_test():
await init_beanie(database=client.drinks,
document_models=[Drinks, Ingredients])
yield
app = FastAPI(
title="API Test",
description="Test API",
version="0.1.0",
)
# All the routers included
app.include_router(router)
if __name__ == "__main__":
uvicorn.run("main:app", host="localhost",
port=8080, reload=True, log_level="debug") The error it gives me is:
@roman-right you think it could be a bug? My packages are: Best regards, Diego. |
Beta Was this translation helpful? Give feedback.
-
@hgalytoby thank you very much! I wouldn't be able to solve it like you. I still have one problem... how to do in the POST/PUT case? @router.post("/", response_model=IngredientsResponse)
async def create_item(
item: Ingredients, # This is the document, which causes the error even here
):
created_item = await item.create() # If I try to use a modified BaseModel, it do not have .create()
print(f"Created Item: {created_item}")
return created_item
# Similar problem with PUT
@router.put("/{item_id}", response_model=IngredientsResponse)
async def update_item(
item: Ingredients,
item_id: PydanticObjectId,
item_to_be_updated: Ingredients
):
await item_to_be_updated.set(item.model_dump(exclude_unset=True))
return await Ingredients.get(item_id, fetch_links=True, nesting_depth=1) Thanks in advance |
Beta Was this translation helpful? Give feedback.
-
I think I did it, don't know if is the best way... # I created a Create BaseModel to use in Post without the BackLink
class IngredientsCreate(BaseModel):
name: Indexed(str, unique=True)
# My POST uses it to the Json of the documentation and to validate, and then I convert it to the Document with BackLink and create the item
@router.post("/", response_model=IngredientsResponse)
async def create_item(
item: IngredientsCreate,
):
new_item_document = Ingredients(**item.model_dump())
created_item = await new_item_document .create()
return await Ingredients.get(created_item.id, fetch_links=True, nesting_depth=1, projection_model=IngredientsResponse) Thanks!! |
Beta Was this translation helpful? Give feedback.
Your workaround is nice.
You can do things like this also:
File a_circular_import.py:
File b_circular_import.py:
But I like your solution more