The problem is that I'm using a custom exception class implemented over attrs that by default sets __hash__ = None as probably all sane mutable classes should do (some background in python-attrs/attrs#136):
In [11]: import attr
...: @attr.s
...: class MyException(Exception):
...: f = attr.ib()
...: set([MyException('foo')])
...:
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-eb39bb116374> in <module>()
3 class MyException(Exception):
4 f = attr.ib()
----> 5 set([MyException('foo')])
TypeError: unhashable type: 'MyException'
I tried fixing this on attrs side by using frozen= which assures the library that the resulting class will be immutable and it's safe to generate a __hash__ function. But then I realized I'm attaching a dictionary to the exception and dictionaries are mutable themselves, so the result is still unhashable:
In [14]: import attr
...: @attr.s(frozen=True)
...: class MyException(Exception):
...: f = attr.ib()
...: set([MyException({'foo': 'bar'})])
...:
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-14-16b4c8ca538e> in <module>()
3 class MyException(Exception):
4 f = attr.ib()
----> 5 set([MyException({'foo': 'bar'})])
<attrs generated hash 926d52d2b56cfd446b915894a46e5c7977941b62> in __hash__(self)
2 return hash((
3 -172549962914795092,
----> 4 self.f,
5 ))
TypeError: unhashable type: 'dict'
In [16]: set([Exception({'foo': 'bar'})])
Out[16]: {Exception({'foo': 'bar'})}
But probably only because their hash function depends on id(...) and not on the content.
In [17]: set([Exception({'foo': 'bar'}), Exception({'foo': 'bar'})])
Out[17]: {Exception({'foo': 'bar'}), Exception({'foo': 'bar'})}
In [18]: set([Exception({'foo': 'bar'})]*2)
Out[18]: {Exception({'foo': 'bar'})}
So a really straighforward way to fix this would be to deduplicate exceptions only by their id. A slightly more convoluted one would be to create a EnsureHashable wrapper object that would try calculating the wrapped exception's hash falling back to object.__hash__ implementation. If that's OK I could send in a PR doing either of those two.
Also, it would be nice if raven reported some sort of a generic "Error when capturing exception" error so that sentry could send an alert. Given that my service didn't experience any connectivity problems, I expected raven/sentry duo to notify me about any problems via email. However, in this case it didn't work like that, and I discovered this problem while going through the logs on a completely unrelated matter.
I've run into this recently. After a rather short debugging session I've narrowed the culprit down to these lines:
https://github.com/immerrr/raven-python/blob/master/raven/events.py#L51-L52
The problem is that I'm using a custom exception class implemented over attrs that by default sets
__hash__ = None
as probably all sane mutable classes should do (some background in python-attrs/attrs#136):I tried fixing this on attrs side by using
frozen=
which assures the library that the resulting class will be immutable and it's safe to generate a__hash__
function. But then I realized I'm attaching a dictionary to the exception and dictionaries are mutable themselves, so the result is still unhashable:Interestingly enough, builtin exceptions allow mutable fields:
But probably only because their hash function depends on
id(...)
and not on the content.So a really straighforward way to fix this would be to deduplicate exceptions only by their
id
. A slightly more convoluted one would be to create aEnsureHashable
wrapper object that would try calculating the wrapped exception's hash falling back toobject.__hash__
implementation. If that's OK I could send in a PR doing either of those two.Also, it would be nice if raven reported some sort of a generic "Error when capturing exception" error so that sentry could send an alert. Given that my service didn't experience any connectivity problems, I expected raven/sentry duo to notify me about any problems via email. However, in this case it didn't work like that, and I discovered this problem while going through the logs on a completely unrelated matter.