Pydantic exclude field from __eq__ to avoid recursion error

Question:

I have a pydantic model like this:

class SomeModel(pydantic.BaseModel):
    name: str
    content: str
    previous_model: typing.Optional["SomeModel"] = None

My code look like this, this is greatly simplified, In my real code there are many and circular dependencies occur by chance occasionally rather than being purposefully created:

models = [
   SomeModel("bob", "2"),
   SomeModel("bob", "2"),
]
models[0].previous_model = models[1]
models[1].previous_model = models[0]

models.remove(models[0])

This throws the following error:

File "c:Usersusernameproject-namesrcmain.py", line 108, in run_all
    models.remove(models[0])
  File "pydanticmain.py", line 902, in pydantic.main.BaseModel.__eq__
  File "pydanticmain.py", line 445, in pydantic.main.BaseModel.dict
  File "pydanticmain.py", line 861, in _iter
  File "pydanticmain.py", line 736, in pydantic.main.BaseModel._get_value
  File "pydanticmain.py", line 445, in pydantic.main.BaseModel.dict
  File "pydanticmain.py", line 861, in _iter
  File "pydanticmain.py", line 736, in pydantic.main.BaseModel._get_value
  File "pydanticmain.py", line 445, in pydantic.main.BaseModel.dict
  File "pydanticmain.py", line 861, in _iter
  File "pydanticmain.py", line 736, in pydantic.main.BaseModel._get_value
  File "pydanticmain.py", line 445, in pydantic.main.BaseModel.dict
  File "pydanticmain.py", line 861, in _iter
  File "pydanticmain.py", line 736, in pydantic.main.BaseModel._get_value
  File "pydanticmain.py", line 445, in pydantic.main.BaseModel.dict
  File "pydanticmain.py", line 861, in _iter

... Snip several hunderd more lines

  File "pydanticmain.py", line 734, in pydantic.main.BaseModel._get_value
  File "pydanticmain.py", line 304, in pydantic.main.ModelMetaclass.__instancecheck__
RecursionError: maximum recursion depth exceeded

I don’t really need the previous_model field to be included in the equality at all. Is there a way to exclude it so that my stack doesn’t overflow? This field is irrelevant for the purpose equality in this case.

Asked By: mousetail

||

Answers:

When pydantic generates __repr__, it iterates over its arguments. That makes sense, that’s one of the key selling points. But it doesn’t work well in your scenario, you’d have to omit previous_node from __repr__ to make it work. You can either skip previous_node in __repr_args__ or return something simpler in __repr__. To give you a very simplified example that is working


import typing

import pydantic


class SomeModel(pydantic.BaseModel):
    name: str
    content: str
    previous_model: typing.Optional["SomeModel"] = None

    def __repr__(self):
        return self.name


SomeModel.update_forward_refs()


models = [
   SomeModel(name="bob", content="2"),
   SomeModel(name="bob", content="2"),
]
models[0].previous_model = models[1]
models[1].previous_model = models[0]

models.remove(models[0])

print(models)

Less simple version that’s closer to how pydantic behaves but will also work in your case


import typing

import pydantic


class SomeModel(pydantic.BaseModel):
    name: str
    content: str
    previous_model: typing.Optional["SomeModel"] = None

    def __repr_args__(self, *args, **kwargs):
        args = self.dict(exclude={'previous_model',})
        return list(args.items())


SomeModel.update_forward_refs()


models = [
   SomeModel(name="bob", content="2"),
   SomeModel(name="bob", content="2"),
]
models[0].previous_model = models[1]
models[1].previous_model = models[0]

models.remove(models[0])
Answered By: Tom Wojcik