[BUG] AttributeError: get_motor_collection when setting fetch_links=True
Describe the bug
AttributeError: get_motor_collection when setting fetch_links=True.
To Reproduce I'm using Beanie with FastAPI. For example, my Beanie models look like this:
class Project(Document):
id: PydanticObjectId = Field(default_factory=PydanticObjectId)
items: list[Link[ItemDB]] | None = None
class ItemDB(Document):
creator: Indexed(str)
When getting a Project by ID, I also want to prefetch its related items. So, my endpoint looks like this:
@router.get('/{project_id}', status_code=status.HTTP_200_OK, summary='Get a project',
response_model_exclude_none=True)
async def get_project(project_id: str) -> Project:
project = await Project.get(project_id, fetch_links=True)
if not project:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return project
But I got the error:
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "my-project/.venv/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py", line 419, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
await super().__call__(scope, receive, send)
File "my-project/.venv/lib/python3.12/site-packages/starlette/applications.py", line 113, in __call__
await self.middleware_stack(scope, receive, send)
File "my-project/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in __call__
raise exc
File "my-project/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in __call__
await self.app(scope, receive, _send)
File "my-project/.venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
File "my-project/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app
raise exc
File "my-project/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app
await app(scope, receive, sender)
File "my-project/.venv/lib/python3.12/site-packages/starlette/routing.py", line 715, in __call__
await self.middleware_stack(scope, receive, send)
File "my-project/.venv/lib/python3.12/site-packages/starlette/routing.py", line 735, in app
await route.handle(scope, receive, send)
File "my-project/.venv/lib/python3.12/site-packages/starlette/routing.py", line 288, in handle
await self.app(scope, receive, send)
File "my-project/.venv/lib/python3.12/site-packages/starlette/routing.py", line 76, in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
File "my-project/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 62, in wrapped_app
raise exc
File "my-project/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 51, in wrapped_app
await app(scope, receive, sender)
File "my-project/.venv/lib/python3.12/site-packages/starlette/routing.py", line 73, in app
response = await f(request)
^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/fastapi/routing.py", line 301, in app
raw_response = await run_endpoint_function(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/fastapi/routing.py", line 212, in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "my-project/app/routers/project.py", line 46, in get_project
project = await Project.get(project_id, fetch_links=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/beanie/odm/documents.py", line 276, in get
return await cls.find_one(
^^^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/beanie/odm/queries/find.py", line 1024, in __await__
document = yield from self._find_one().__await__() # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/beanie/odm/queries/find.py", line 982, in _find_one
return await self.document_model.find_many(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/beanie/odm/queries/find.py", line 689, in first_or_none
res = await self.limit(1).to_list()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/beanie/odm/queries/cursor.py", line 67, in to_list
cursor = self.motor_cursor
^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/beanie/odm/queries/find.py", line 661, in motor_cursor
self.build_aggregation_pipeline()
File "my-project/.venv/lib/python3.12/site-packages/beanie/odm/queries/find.py", line 610, in build_aggregation_pipeline
construct_lookup_queries(
File "my-project/.venv/lib/python3.12/site-packages/beanie/odm/utils/find.py", line 29, in construct_lookup_queries
construct_query(
File "my-project/.venv/lib/python3.12/site-packages/beanie/odm/utils/find.py", line 271, in construct_query
"from": link_info.document_class.get_motor_collection().name, # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "my-project/.venv/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py", line 242, in __getattr__
raise AttributeError(item)
AttributeError: get_motor_collection
If I remove fetch_links=True and later on call
await project.fetch_all_links()
then it works fine.
I'm sure that the database was initialized properly because everything else works normally, only this fetch_links has problem. Just to be sure, here is how the database was initialized:
async def init_db(db_url: str):
client = AsyncIOMotorClient(host=db_url, tz_aware=True)
await init_beanie(
database=client.get_default_database(default='myDB'),
document_models=[Project, ItemDB],
allow_index_dropping=True
)
This function is then called in the startup part of FastAPI:
@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db(settings.db_url)
yield
app = FastAPI(
lifespan=lifespan
)
So, did I miss something? Or it's a bug of Beanie? I'm using FastAPI 0.115.2 and Beanie 1.27.0.
Expected behavior
Document.get(id, fetch_links=True) should work without error.
This issue is stale because it has been open 30 days with no activity.
This issue was closed because it has been stalled for 14 days with no activity.
I want to keep this issue open. Could someone confirm this bug? Or is it the problem from my side?
Hi @tdoan2010, thank you for your report and apologies for the issue being closed. I can confirm that there is a bug somewhere with the links loading. I am currently working on a serialization/deserialization bugfix, which might also fix this issue. The bugfix will however only be applicable for when using Beanie with Pydantic v2, and will remain broken on Pydantic v1 according to my initial testing (there's no way to fix it for Pydantic v1).
Thank you for your reply and your work!
I think it's fine that it only works with Pydantic v2. At some point, one needs to upgrade Pydantic anyway.
I am still encountering this error in version 1.29.0
Hi @mbUSC, please show under which circumstances you get this issue i.e. please provide a script that reproduces your issue. Also please mention your environment (Python, Pydantic, FastAPI, .., versions). Thanks
i'm also getting the same error, sharing the code to reproduce the error
Versions
requires-python = ">=3.13,<4.0"
"fastapi[standard] (>=0.116.1,<0.117.0)",
"uvicorn[standard] (>=0.35.0,<0.36.0)",
"pydantic-settings (>=2.10.1,<3.0.0)",
"beanie (>=2.0.0,<3.0.0)",
"motor (>=3.7.1,<4.0.0)",
Model:
Model:
class APIKeys(Document):
account: Link[Accounts]
name: str
api_key: Annotated[str, Indexed(unique=True)]
Api:
@service_router.get(
"/test",
status_code=status.HTTP_204_NO_CONTENT
)
async def test():
records = await APIKeys.find(APIKeys.name=="test",fetch_links=True).to_list()
return
this is raising the below error :
ckages/beanie/odm/queries/find.py", line 675, in get_cursor
await self.document_model.get_pymongo_collection().aggregate(
...<3 lines>...
)
TypeError: object AsyncIOMotorLatentCommandCursor can't be used in 'await' expression
i have tried the same using beanie==1.29.0 Its working . . . issue is in the newer versions
i have tried the same using beanie==1.29.0 Its working . . . issue is in the newer versions
If you want to upgrade to Beanie 2.0.0, @Rashicom, you will have to replace Motor with Pymongo Async since Beanie 2.0.0 no longer supports Motor.