1

I'm using pydantic to model objects which are then being serialized to json and persisted in mongodb For better encapsulation, I want to some fields to be private but I still want them to be serialized to json when saving to mongodb, and then deserialized back from json when I fetch the object from the db

how can this be done?

Example Model:

class MyModel(BaseModel):
    public_field: str
    _creation_time: str

    def __init__(self, public_field: str):
        super().__init__(public_field=public_field,
                         _creation_time=str(datetime.now()))


model = MyModel(public_field='foo')
json_str = model.json()
print(json_str)

The output of this code is:

{"public_field": "foo"}

I would like it to be something like this:

{"public_field": "foo", "_creation_time": "2023-03-03 09:43:47.796720"}

and then also to be able to deserialize the above json back with the private field populated

Nir Brachel
  • 413
  • 3
  • 12
  • Related [thread](https://stackoverflow.com/questions/74552121/private-attribute-but-a-pydantic-field), unfortunately there is no answer yet. A new feature called [computed fields](https://github.com/pydantic/pydantic/pull/2625) may be available in pydantic v2, and if you really only want to parse leading underscore from serialization [this thread](https://stackoverflow.com/questions/59562997/how-to-parse-and-read-id-field-from-and-to-a-pydantic-model) should answer that. An [alternative approach](https://stackoverflow.com/questions/69152143/private-attributes-in-pydantic) to private fields. – metatoaster Mar 05 '23 at 05:09

1 Answers1

1

Pydantic doesn't really like this having these private fields. By default it will just ignore the value and is very strict about what fields get set.

One way around this is to allow the field to be added as an Extra (although this will allow more than just this one field to be added).

The following works as you might expect:

class MyModel(BaseModel, extra=Extra.allow):
    public_field: str
    _creation_time: str

    def __init__(self, **kwargs):
        kwargs.setdefault("_creation_time", str(datetime.now()))
        super().__init__(**kwargs)
model = MyModel(public_field='foo')
print(model)
json_str = model.json()
print(json_str)
print(MyModel.parse_obj({"public_field": "foo", "_creation_time": "2023-03-05 00:08:21.722193"}))
public_field='foo' _creation_time='2023-03-05 00:12:35.554712'
{"public_field": "foo", "_creation_time": "2023-03-05 00:12:35.554712"}
public_field='foo' _creation_time='2023-03-05 00:08:21.722193'

Note that because the field itself is being ignored, setting Field(default_factory=...) will not work, so you're stuck with the more awkward __init__ method,

flakes
  • 21,558
  • 8
  • 41
  • 88
  • so if I have multiple private fields it's all or nothing? I can't control which fields get serialized? – Nir Brachel Mar 05 '23 at 19:10
  • 1
    @NirBrachel You still could, but you would need to provide a custom json encoder to the class which does the filtering for you. Pydantic is best used for having 1:1 representations in code of what represents the serialized data. Once you start adding things like private fields, its going against that mindset. – flakes Mar 05 '23 at 19:19