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

Add public BaseModel.iter() method #997

Closed
Bobronium opened this issue Nov 14, 2019 · 13 comments
Closed

Add public BaseModel.iter() method #997

Bobronium opened this issue Nov 14, 2019 · 13 comments
Assignees
Labels
dumping how pydantic serialises models, e.g. via `.dict()` and `.json()` feature request unconfirmed Bug not yet confirmed as valid/applicable

Comments

@Bobronium
Copy link
Contributor

Bobronium commented Nov 14, 2019

Feature Request

Add BaseModel.iter() method which will behave exactly like a dict, but without converting nested values to dict by default and will be a generator instead

Why

Imagine you want to iterate through values in model, but also need to exclude some, or use aliases instead of attributes. So what you need to do is write something like this:

for attr, value in model.dict(exclude={...}, by_alias=True).items():
    do_something(attr, value)

Obviously, in this case python will firet evaluate model.dict(exclude={...}, by_alias=True). part, going through all values and only then you will be able to iterate, going through the same values again.

Also, by using dict nested values are force-converted to dict, and this might be very slow when we just want to iterate through top-level values.

We also cannot use existing _iter(), because some work is actually done in dict()

How we can solve it

By having iter() method we can bypass this double-iteration problem and converting-to-dict problems:

for attr, value in model.iter(exclude={...}, by_alias=True):
    do_something(attr, value)

Selected Assignee: @hramezani

@samuelcolvin
Copy link
Member

This already exists!

It's called ._iter().

@samuelcolvin
Copy link
Member

Should be documented I guess.

@Bobronium
Copy link
Contributor Author

Bobronium commented Nov 14, 2019

This already exists!
It's called ._iter().

Yeah, but I mentioned, why it cannot be used in this case:

We also cannot use existing _iter(), because some work is actually done in dict()

By some work i mean resolving aliases keys and excluding the values

@Bobronium Bobronium changed the title Add BaseModel.iter() method Add public BaseModel.iter() method Nov 14, 2019
@samuelcolvin
Copy link
Member

I don't want to add more public method to models, best I think to implement this yourself.

If there's a simple way to achieve this with changes to existing functions of happy review a PR.

@samuelcolvin samuelcolvin reopened this Nov 14, 2019
@samuelcolvin
Copy link
Member

Sorry, I missed your comment when replying in a hurry.

@Bobronium
Copy link
Contributor Author

I don't want to add more public method to models, best I think to implement this yourself.

Just to be clear, thats valid even if ._iter() itself would become public without adding any new methods?

If there's a simple way to achieve this with changes to existing functions of happy review a PR.

I guess the simplest way would be to move those excluding and aliases stuff from .dict() to ._iter(), that will also simplify .dict() too.

I'll try to play with it soon and will be happy to make a PR once it'll be done.

@dmontagu
Copy link
Contributor

dmontagu commented Nov 14, 2019

For what it's worth, I would be in favor of moving logic from dict into _iter, assuming it didn't result in meaningful performance penalties in any public methods -- I think it would simplify/result in a smaller diff for the implementation of dump_as_type from #812, and the related serialization performance improvements (even when not dumping as a specified type).

@Kludex
Copy link
Member

Kludex commented Apr 25, 2023

On V2, we can leverage .model_dump() as follows:

from pydantic import BaseModel


class Foo(BaseModel):
    a: str
    b: int


foo = Foo(a="a", b=1)

for key, value in foo.model_dump().items():
    print(key, value)

For that reason, I'll be closing this issue. 🙏

@Kludex Kludex closed this as completed Apr 25, 2023
@gsakkis
Copy link
Contributor

gsakkis commented Aug 21, 2023

@Kludex model_dump is not equivalent to _iter if there are fields with custom serialization. Here's an example for a bson.ObjectId field serialized as string.

import bson
from pydantic import BaseModel, Field
from pydantic_core import core_schema


class PydanticObjectId(bson.ObjectId):
    @classmethod
    def __get_pydantic_core_schema__(cls, source_type, handler):
        serialization = core_schema.plain_serializer_function_ser_schema(str)
        return core_schema.is_instance_schema(cls, serialization=serialization)


class Foo(BaseModel):
    id: PydanticObjectId = Field(default_factory=PydanticObjectId)
    b: int


foo = Foo(b=1)
print(f"str: {foo}")
print(f"_iter: {dict(foo._iter())}")
print(f"model_dump: {foo.model_dump()}")

Output:

str: id=ObjectId('64e3afb883f5e2aaed388877') b=1
_iter: {'id': ObjectId('64e3afb883f5e2aaed388877'), 'b': 1}
model_dump: {'id': '64e3afb883f5e2aaed388877', 'b': 1}

So unless there is a way to prevent custom serialization, I'd suggest to reopen this issue.

@Kludex Kludex reopened this Aug 22, 2023
@pydantic-hooky pydantic-hooky bot added the unconfirmed Bug not yet confirmed as valid/applicable label Aug 22, 2023
@samuelcolvin
Copy link
Member

no longer really relevant in V2 - _iter has gone, replaced by the underlying methods in pydantic-core.

@gsakkis
Copy link
Contributor

gsakkis commented Sep 15, 2023

An example of how the previous example would work by using pydantic-core methods instead of _iter would help.

@bojanbg
Copy link

bojanbg commented Nov 23, 2023

@Kludex model_dump is not equivalent to _iter if there are fields with custom serialization. Here's an example for a bson.ObjectId field serialized as string.

So unless there is a way to prevent custom serialization, I'd suggest to reopen this issue.

I think there is a problem with your PydanticObjectId definition. If I use the one from here, this is the output I get running your example:

str: id=ObjectId('655f16f55d40bae3a225723f') b=1
_iter: {'id': ObjectId('655f16f55d40bae3a225723f'), 'b': 1}
model_dump: {'id': ObjectId('655f16f55d40bae3a225723f'), 'b': 1}

alexdrydew pushed a commit to alexdrydew/pydantic that referenced this issue Dec 23, 2023
@thusithaC
Copy link

So what was the final verdict? Can we use model_dump instead of _iter?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dumping how pydantic serialises models, e.g. via `.dict()` and `.json()` feature request unconfirmed Bug not yet confirmed as valid/applicable
Projects
None yet
Development

No branches or pull requests

8 participants