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] Beanie projection and Pydantic Schema do not play well together #892

Open
sheoak opened this issue Mar 5, 2024 · 6 comments
Open
Labels
bug Something isn't working

Comments

@sheoak
Copy link

sheoak commented Mar 5, 2024

Describe the bug
Beanie projections are expecting an "_id" field, but Pydantic schema expect "id". This makes it impossible to use the same Schema and create duplicated code (unless I’m missing the proper method to do it)

To Reproduce

from motor.motor_asyncio import AsyncIOMotorClient
from pydantic import BaseModel, Field

from beanie import Document, init_beanie, PydanticObjectId

class Author(Document, BaseModel):
    name: str

class AuthorRead(BaseModel):
    id: PydanticObjectId = Field(alias="id")
    name: str

class AuthorProjection(BaseModel):
    # note the underscore
    id: PydanticObjectId = Field(alias="_id")
    name: str

async def example():
    client = AsyncIOMotorClient("mongodb://localhost:27017")
    await init_beanie(database=client.db_name, document_models=[Author])

    dict = { "name": "Joe" }
    joe = Author(**dict)
    await joe.insert()

    # created object contains "id"
    print(AuthorRead(**joe.dict()))

    # Beanie get, also give us an 'id' field, so AuthorRead expect id too
    # (get() method does not have a project() method)
    result = await Author.get(joe.id)
    print(AuthorRead(**joe.dict()))

    # projection is expecting "_id", not "id"
    # we cannot use the same Schema!
    result = await Author.find_all().project(AuthorProjection).to_list()
    print(result)

await example()

Expected behavior
A way to use the same Schema for projections, like mapping _id to id during projection

@sheoak
Copy link
Author

sheoak commented Mar 5, 2024

To give more context, here is my dirty fix on fastapi. I’m really not satisfied with that but that’s all I could find:

@app.get("/hack", response_model=list[AuthorRead])
async def hack() -> list[Any]:
    # using a new Schema just for the hack. AuthorRead should be usable
    return await Author.find_all().project(AuthorProjection).to_list()

@CartfordSam
Copy link

I can post a gist in a bit, but I use a base class that overrides the BaseModel and handles the _id conversion there, both directions. Otherwise lots of ways to tackle this, most are so-so, but more of a pydantic quirk than anything else I think!

@sheoak
Copy link
Author

sheoak commented Mar 11, 2024

@CartfordSam That would be nice thank you!
I think it should be included in Beanie nevertheless, it complexify things for no reason.

@valentinoli
Copy link
Contributor

valentinoli commented Mar 12, 2024

In FastAPI you can use response_model_by_alias=False to declare you want the response model to not use the alias.

That way, you don't even need a projection if the only purpose for it is to ensure you have id and not _id. So your FastAPI path operation can be written like:

@app.get("/hack", response_model=list[Author], response_model_by_alias=False)
async def hack() -> list[Any]:
    return await Author.find_all().to_list()

@sheoak
Copy link
Author

sheoak commented Mar 15, 2024

response_model_by_alias=False

Thanks, I misunderstood what it was doing, it seems to work indeed. Then i suppose using the format that works for projection is suitable.
I feel that this would be a nice thing to add in the documentation maybe?

@CartfordSam
Copy link

@CartfordSam That would be nice thank you! I think it should be included in Beanie nevertheless, it complexify things for no reason.

yeah complexify is right lol
response_model_by_alias is probably the right way to go, especially if you can set it at the router level, here's my janky workaround YMMV

@roman-right roman-right added the bug Something isn't working label Apr 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants