collerek/ormar

`__hash__` TypeError in v0.12.1

Closed this issue Β· 6 comments

Describe the bug

After upgrading from v0.12.0 to v0.12.1 my test suite fails with a lot of these errors:

self = Service({'id': None, 'created_at': None, 'updated_at': None, 'more sensitive fields')
name = 'sensitive', value = UUID('53cadcf0-cdce-4101-8218-22c96d3edd39')

    def __setattr__(self, name: str, value: Any) -> None:  # noqa CCR001
        """
        Overwrites setattr in pydantic parent as otherwise descriptors are not called.
    
        :param name: name of the attribute to set
        :type name: str
        :param value: value of the attribute to set
        :type value: Any
        :return: None
        :rtype: None
        """
>       prev_hash = hash(self)
E       TypeError: __hash__ method should return an integer

.venv/lib/python3.11/site-packages/ormar/models/newbasemodel.py:184: TypeError

I'm not sure, but I think every ormar model initialization fails with the same error. Any clue as to why? πŸ™‚

Can you provide a sample code to reproduce? Cause the whole tet suite pass on win and linux for python 3.7-3.10 πŸ˜…

Looks like the issue goes away if I specify id on my models. The id field is auto-incrementing and defined on a base model like this:

class ModelBase(ormar.Model):
    id: int = ormar.Integer(autoincrement=True, primary_key=True)
    created_at: datetime = ormar.DateTime(timezone=True, server_default=func.now())
    updated_at: datetime = ormar.DateTime(timezone=True, server_default=func.now(), onupdate=func.now())

    class Meta:
        abstract = True

When I run MyModel.objects.create(...) without an explicit id it fails with the above error.

In the __setattr__ method, for prev_hash = hash(self), self is a model instance with id set as None. Might that be relevant?


Just adding this here in case that's enough to shed some light on the issue. Will try to dedicate some more time to creating a reproducible example later if needed πŸ‘

Well, it's calculated as this:

    def __hash__(self) -> int:
        if getattr(self, "__cached_hash__", None) is not None:
            return self.__cached_hash__ or 0

        if self.pk is not None:
            ret = hash(str(self.pk) + self.__class__.__name__)
        else:
            vals = {
                k: v
                for k, v in self.__dict__.items()
                if k not in self.extract_related_names()
            }
            ret = hash(str(vals) + self.__class__.__name__)

        object.__setattr__(self, "__cached_hash__", ret)
        return ret

It might be the case that it does not play well in some cases with inheritance (although we have tests for that too, not sure if base classes have id's though, don't remember).

Ok, I'll come back with a full reproducible example when I get a chance πŸ‘

I'm sorry @collerek, this is a false positive. After a little bit of digging, I found this πŸ˜“

class ModelBase(ormar.Model):
    id: int = ormar.Integer(autoincrement=True, primary_key=True)
    created_at: datetime = ormar.DateTime(timezone=True, server_default=func.now())
    updated_at: datetime = ormar.DateTime(timezone=True, server_default=func.now(), onupdate=func.now())

    class Meta:
        abstract = True
 
   # about 100 lines of code here

    def __hash__(self) -> int:  # <-- this is the culprit!
        return self.id

My bad. Thanks for the help!

No worries, glad that it's sorted out ☺️