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

model_validate: exclude #8192

Open
4 of 13 tasks
mustafa0x opened this issue Nov 21, 2023 · 8 comments
Open
4 of 13 tasks

model_validate: exclude #8192

mustafa0x opened this issue Nov 21, 2023 · 8 comments
Assignees
Labels
feature request help wanted Pull Request welcome

Comments

@mustafa0x
Copy link

mustafa0x commented Nov 21, 2023

Initial Checks

  • I have searched Google & GitHub for similar requests and couldn't find anything
  • I have read and followed the docs and still think this feature is missing

Description

Lazy loaded orm fields are loaded inadvertently by model_validate. I'd like to be able to exclude fields from model_validate.

Previously discussed here #6861.

With the release of pydantic v2, is it possible to load a model and exclude certain fields when loading using the new model_validate method? I was hoping the new context parameter was able to achieve this, but it looks this does not quite do what I was thinking.

Affected Components

@sydney-runkle
Copy link
Member

@mustafa0x,

Could you use the extra='ignore' config setting? https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.extra

@sydney-runkle sydney-runkle self-assigned this Dec 1, 2023
@mustafa0x
Copy link
Author

mustafa0x commented Dec 1, 2023

Thanks for the reply @sydney-runkle. That seems to be for a different use case.

#6861 explains the intent quite well.

My use case is very similar to this issue #1806, when loading models from an ORM object, if there are related objects within the main ORM object, then they would be lazily loaded. I want to be be able to exclude certain fields from being lazily loaded.

fastapi's response_model_exclude also makes plain the goal here.

You can also use the path operation decorator parameters response_model_include and response_model_exclude.

They take a set of str with the name of the attributes to include (omitting the rest) or to exclude (including the rest).

This can be used as a quick shortcut if you have only one Pydantic model and want to remove some data from the output.

(emphasis mine)

@axelmukwena
Copy link

axelmukwena commented Dec 14, 2023

@mustafa0x I had the same issue too and here is how I solved it. I used noload to explicitly tell sqlalchemy to not trigger a relationship load when getting many items, however do it when getting one item

class RoleOrm(OrmBase):
    __tablename__ = "roles"

    # Columns
    uid: Mapped[str] = mapped_column(
        default=lambda: str(uuid4().hex),
        primary_key=True,
        index=True,
        nullable=False,
        unique=True,
    )

    # Relationships
    role_permissions: Mapped[
        list["RolePermissionOrm"]  # noqa: F821 # type: ignore
    ] = relationship(cascade="all, delete-orphan", back_populates="role", lazy="joined")

Role Model

class Role(RoleBase, RoleBlame, RoleAutoGenerated):
    role_permissions: list[RolePermission] | None = Field(
        default=None,
        description="The permissions that the role has",
    )

In Controller/Repository

from fastapi import HTTPException
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import noload

class RoleRepository:
    @staticmethod
    async def get_by_uid(db: AsyncSession, uid: str) -> Role:
        statement = select(RoleOrm).where(RoleOrm.uid == uid)
        results = await db.execute(statement)
        role_orm = results.unique().scalar_one_or_none()
        if not role_orm:
            raise HTTPException(
                status_code=404,
                detail="Role with given id does not exist",
            )
        role = Role.model_validate(role_orm)
        return role

    @staticmethod
    async def get_many(
        db: AsyncSession, skip: int = 0, limit: int = 20
    ) -> tuple[list[Role], str]:
        count = await db.scalar(select(func.count(RoleOrm.uid)))
        if not count:
            return [], "0"
        statement = (
            select(RoleOrm)
            .options(noload(RoleOrm.role_permissions))
            .offset(skip)
            .limit(limit)
        )
        results = await db.execute(statement)
        roles = [Role.model_validate(orm) for orm in results.unique().scalars().all()]
        return roles, str(count)

The result on get_many now just gives me

"role_permissions": []

@mustafa0x
Copy link
Author

Thanks @axelmukwena! It seems like that would work, but I prefer to not have to resort to that. I'm hoping pydantic v2 can add back this v1 feature. :)

@ZackPlauche
Copy link

I really hope they add this one! I can't believe there's no standard way of handling this!

@NicolaiLolansen
Copy link

+1 for this functionality!

jdp1ps added a commit to CRISalid-esr/svp-harvester that referenced this issue Feb 16, 2024
jdp1ps added a commit to CRISalid-esr/svp-harvester that referenced this issue Feb 16, 2024
jdp1ps added a commit to CRISalid-esr/svp-harvester that referenced this issue Apr 4, 2024
@pedrorjbr
Copy link

+1

@hmarcuzzo
Copy link

+1 for this functionality!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request help wanted Pull Request welcome
Projects
None yet
Development

No branches or pull requests

7 participants