pydantic-core icon indicating copy to clipboard operation
pydantic-core copied to clipboard

The keyword argument `exclude` does not take effect when custom class uses `model_serializer`

Open BreezeWhite opened this issue 1 year ago • 3 comments

Basic System Info

  • python version: 3.11.2
  • pydantic version: 2.0.2
  • Platform: MacOS 13.2.1 M2 chip

Description

The exclude keyword argument of model_dump function does not work when there is a custom serialization function decorated with model_serializer(when_used='json'). I would expect the custom serialization process will only be invoked when using model_dump_json or model_dump(mode='json'), but it seems like this will also affects the normal model_dump behaviour.

Take the following code as example:

from pydantic import BaseModel, model_serializer


class Test(BaseModel):
  a: int = 123
  b: str = 'bob'

  @model_serializer(when_used='json')
  def ser_model(self):
    out = {}
    for k, v in self:
      if isinstance(v, str):
        out[k] = v.title()
      else:
        out[k] = v
    return out


t = Test()

# This prints normally
print(t.model_dump())  # {'a': 123, 'b': 'bob'}

# This also works as expected
print(t.model_dump_json())  # {'a': 123, 'b': 'Bob'}

# But this doesn't
print(t.model_dump(exclude={'b'}))  # {'a': 123, 'b': 'bob'}

The last case of using exclude in model_dump does not really exclude the specified column in the output. Everything would just work fine by removing the ser_model function, therefore the problem must be within ser_model itself.

I have also tried to pass the mode='wrap' to model_serializer and redefined the function signature as documented to the following:

from typing import Any
from pydantic import BaseModel, model_serializer


class Test(BaseModel):
  a: int = 123
  b: str = 'bob'

  @model_serializer(mode='wrap', when_used='json')
  def ser_model(self, serializer: Any, handler: pydantic.SerializerFunctionWrapHandler):
    out = {}
    for k, v in self:
      if isinstance(v, str):
        out[k] = v.title()
      else:
        out[k] = v
    return serializer(out)


t = Test()

# Still doesn't work
print(t.model_dump(exclude={'b'}))  # {'a': 123, 'b': 'bob'}

The above still doesn't work, and after testing all different cases, I suspect the model_serializer not only overrides the serialization process to 'json' mode only, but also to other scenarios even if the argument when_used='json'. I tried removing when_used in model_serializer to make it apply to both json and dict dump cases, and it 'kind of' works, rather I received a UserWarning saying that the Pydantic serializer expected type of Test and not dict (this occurs at the last line, the return statement).

To this point, there are three things that confuse me:

  1. How to make model_serializer work correctly given the above scenario?
  2. Why is there so little explanation to the meaning of each argument of model_serialization? Says the usage of mode='wrap', why is the correct function signature of the decorated function being listed in the Usage Error page, and not under the API reference page? For me, I would expect all use cases being listed under API reference page or tutorial page.
  3. Related to the above, the function signature provided in the official document annotated the first additional argument (the serializer defined in the above code example, and the value in the document) as type of Any, which does not give much information of what it really is. And when print the variable type in the console, I can see the actual type is SerializationCallable, which until then I understand I can call it at the last return statement. Isn't it much better to explicitly point this as well in the document? Both the in the API reference and the Usage Error page.

Still thanks for the awesome work and the extraordinary Rust migration. Hope there could be someone helping on this issue.

Selected Assignee: @samuelcolvin

BreezeWhite avatar Jul 10 '23 10:07 BreezeWhite

Thanks for reporting this. I think it's probably worth moving this issue to the pydantic repo even if the fix needs to be in pydantic-core, just for increased visibility.

dmontagu avatar Jul 10 '23 22:07 dmontagu

Oh, I didn't notice that. Thanks for the suggestion. I will open a new issue there as well.

BreezeWhite avatar Jul 11 '23 02:07 BreezeWhite

Think this issue also related to this one https://github.com/pydantic/pydantic/issues/6736

robertofd1995 avatar Aug 08 '23 16:08 robertofd1995

@samuelcolvin @BreezeWhite i think you can close this issue because this one pydantic/pydantic#6736 was closed

donBarbos avatar Jul 04 '24 14:07 donBarbos

Hi @donBarbos . Thanks for the reminding. Closing this issue

BreezeWhite avatar Jul 05 '24 10:07 BreezeWhite