Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Optional Link Field Not Fetching Data in fetch_links #1130

Open
SwAt1563 opened this issue Feb 19, 2025 · 2 comments
Open

[BUG] Optional Link Field Not Fetching Data in fetch_links #1130

SwAt1563 opened this issue Feb 19, 2025 · 2 comments

Comments

@SwAt1563
Copy link

Description

When using fetch_links with an Optional[Link[Answer]] field in a Document, the related document is not fetched properly. Instead, only the required Link fields (template and candidate) are correctly populated.

Code to Reproduce

from typing import Optional
from beanie import Document, Link
from pydantic import Field

class Candidate(Document):
    name: str

class Template(Document):
    title: str

class Answer(Document):
    response: str

class Interview(Document):
    candidate: Link[Candidate]
    template: Link[Template]
    last_answer: Optional[Link[Answer]] = Field(None, title="Last Answer")

# Querying
interview = await Interview.find_one(
    Interview.template.id == template.id,
    Interview.candidate.id == candidate.id,
    fetch_links=True,
    nesting_depth=1
)

Expected Behavior

  • interview.template and interview.candidate should be populated ✅
  • interview.last_answer should also be populated if a linked Answer document exists ✅

Actual Behavior

  • interview.template and interview.candidate are fetched correctly ✅
  • interview.last_answer remains None even when an associated Answer exists ❌

Possible Cause

Since last_answer is an Optional[Link[Answer]], it seems that Beanie does not properly resolve the linked document when using fetch_links. This might be a bug in the way optional links are handled during population.

Environment

  • Python version: 3.12
  • beanieversion: 1.27.0
  • pymongo version: 4.9.2

Suggested Fix

Ensure that Optional[Link[T]] fields are properly populated when using fetch_links=True, similar to non-optional Link[T] fields.

Thanks for looking into this!

@staticxterm
Copy link
Contributor

Hi @SwAt1563, it works fine for me.
This is the script I used:

import asyncio
from typing import Optional

from beanie import Document, Link, WriteRules, init_beanie
from motor.motor_asyncio import AsyncIOMotorClient


class Candidate(Document):
    name: str


class Template(Document):
    title: str


class Answer(Document):
    response: str


class Interview(Document):
    candidate: Link[Candidate]
    template: Link[Template]
    last_answer: Optional[Link[Answer]]


async def main() -> None:
    client = AsyncIOMotorClient("mongodb://localhost:27017")
    database = client["test-db"]

    await init_beanie(
        database, document_models=[Candidate, Template, Answer, Interview]
    )

    # Run DB queries now.
    candidate = Candidate(name="Boris")
    template = Template(title="Template")
    last_answer = Answer(response="Pass")
    interview = Interview(
        candidate=candidate, template=template, last_answer=last_answer
    )
    await interview.save(link_rule=WriteRules.WRITE)
    breakpoint()

    candidate = await Candidate.find_one({})
    template = await Template.find_one({})

    interview = await Interview.find_one(
        Interview.template.id == template.id,
        Interview.candidate.id == candidate.id,
        fetch_links=True,
        nesting_depth=1,
    )
    breakpoint()


if __name__ == "__main__":
    asyncio.run(main())

All documents were successfully inserted to the DB, e.g. the Interview document is:

{
  "_id": {
    "$oid": "67b634943b389f7496628890"
  },
  "candidate": {
    "$ref": "Candidate",
    "$id": {
      "$oid": "67b634943b389f749662888d"
    }
  },
  "last_answer": {
    "$ref": "Answer",
    "$id": {
      "$oid": "67b634943b389f749662888f"
    }
  },
  "template": {
    "$ref": "Template",
    "$id": {
      "$oid": "67b634943b389f749662888e"
    }
  }
}

Script output:

$ python test_links.py
> [REDACTED]\test_links.py(42)main()
-> breakpoint()
(Pdb) c
> [REDACTED]\test_links.py(53)main()
-> breakpoint()
(Pdb) interview
Interview(id=ObjectId('67b634943b389f7496628890'), revision_id=None, candidate=Candidate(id=ObjectId('67b634943b389f749662888d'), revision_id=None, name='Boris'), template=Template(id=ObjectId('67b634943b389f749662888e'), revision_id=None, title='Template'), last_answer=Answer(id=ObjectId('67b634943b389f749662888f'), revision_id=None, response='Pass'))
(Pdb)

You can see that the last_answer field is fetched properly.
Similarly, if I modify the Interview document in DB and set last_answer to "null", and modify the script accordingly to only retrieve the data:

    interview = await Interview.find_one(
        Interview.template.id == ObjectId("67b634943b389f749662888e"),
        Interview.candidate.id == ObjectId("67b634943b389f749662888d"),
        fetch_links=True,
        nesting_depth=1,
    )

Then the output is:

$ python test_links.py 
> [REDACTED]\test_links.py(54)main()
-> breakpoint()
(Pdb) interview
Interview(id=ObjectId('67b634943b389f7496628890'), revision_id=None, candidate=Candidate(id=ObjectId('67b634943b389f749662888d'), revision_id=None, name='Boris'), template=Template(id=ObjectId('67b634943b389f749662888e'), revision_id=None, title='Template'), last_answer=None)
(Pdb)

@SwAt1563, could you please provide more information about which Pydantic version you are using and also an exact script to be able to reproduce your issue. I wasn't able to reproduce what you described.
My environment is: Python 3.13, beanie 1.29.0, Pydantic 2.10.6, motor 3.7.0, pymongo 4.11.1.

@SwAt1563
Copy link
Author

I worked on this issue with my teammate, who has a similar code implementation but different data. After your response, I conducted tests on my side, and everything worked as expected. This suggests that the issue might be on my teammate's side.

I also verified that the interview instance contains last_answer, but it only returns its ID and collection name, not the full field data.

Since I am unable to reproduce the issue on my end, we can close this bug for now. If the problem persists, I will open a new ticket with additional details.

Thank you for your support!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants