From 4de6df04e62c64c431b1aed41b3f48231799d08f Mon Sep 17 00:00:00 2001 From: Andy Freeland Date: Tue, 6 Jun 2017 23:39:36 -0700 Subject: [PATCH] BACKWARDS INCOMPATIBLE: Hash instances by object id rather than value This patch updates our usage of `attr.s` to set `hash=False`, so that we use id-based hashing instead of value-based hashing. attrs 17.1.0 changed the default for `hash` to `None`, which makes objects unhashable. We set `hash=False` so that we can continue to use objects as keys in dictionaries, but without attempting to hash by value. http://www.attrs.org/en/stable/changelog.html --- README.rst | 26 ++++++++++++++++++++++++-- attrs_sqlalchemy.py | 23 +++++++++++++++++++---- test_attrs_sqlalchemy.py | 5 +++-- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 76d50d4..5bc853a 100644 --- a/README.rst +++ b/README.rst @@ -10,8 +10,10 @@ attrs_sqlalchemy :alt: CI status Use the amazing `attrs `_ library to add -``__repr__``, ``__eq__``, ``__cmp__``, and ``__hash__`` methods according to -the fields on a SQLAlchemy model class. +``__repr__``, ``__eq__``, and ``__cmp__`` methods according to the fields on a +SQLAlchemy model class. + +``__hash__`` will always fall back to id-based hashing from ``object``. Example @@ -47,6 +49,26 @@ Installation $ pip install attrs_sqlalchemy +Changelog +========= + +0.2.0 (UNRELEASED) +------------------ + +- **Backward-incompatible**: Apply ``attr.s`` with ``hash=False``, using + id-based hashing instead of value-based hashing. + + attrs 17.1.0 changed the default for ``hash`` to ``None``, which makes + objects unhashable. We set ``hash=False`` so that we can continue to use + objects as keys in dictionaries, but without attempting to hash by value. + + http://www.attrs.org/en/stable/changelog.html + +0.1.0 (2016-09-24) +------------------ + +- Initial release + Project Information =================== diff --git a/attrs_sqlalchemy.py b/attrs_sqlalchemy.py index 6d3b57a..ce86946 100644 --- a/attrs_sqlalchemy.py +++ b/attrs_sqlalchemy.py @@ -26,9 +26,24 @@ def attrs_sqlalchemy(maybe_cls=None): """ - A class decorator that adds ``__repr__``, ``__eq__``, ``__cmp__``, and - ``__hash__`` methods according to the fields defined on the SQLAlchemy - model class. + A class decorator that adds ``__repr__``, ``__eq__``, and ``__cmp__``, + methods according to the fields defined on the SQLAlchemy model class. + + ``__hash__`` will always fall back to id-based hashing from + :class:`object`. + + .. versionchanged:: 0.2.0 + + :func:`attr.s` is applied with ``hash=False``, using id-based hashing + instead of value-based hashing. + + attrs 17.1.0 changed the default for ``hash`` to ``None``, which makes + objects unhashable. + + We set ``hash=False`` so that we can continue to use objects as keys in + dictionaries, but without attempting to hash by value. + + http://www.attrs.org/en/stable/changelog.html """ def wrap(cls): these = { @@ -53,7 +68,7 @@ def wrap(cls): # which won't be ready yet. for name in inspect(cls).columns.keys() } - return attr.s(cls, these=these, init=False) + return attr.s(cls, these=these, init=False, hash=False) # `maybe_cls` depends on the usage of the decorator. It's a class if it's # used as `@attrs_sqlalchemy` but `None` if it's used as diff --git a/test_attrs_sqlalchemy.py b/test_attrs_sqlalchemy.py index 5305617..d97c7e7 100644 --- a/test_attrs_sqlalchemy.py +++ b/test_attrs_sqlalchemy.py @@ -37,9 +37,10 @@ class MyModel(TestBase): # Instances should have a repr containing their keys and type assert repr(instance) == "MyModel(id=1, text='hello')" - # Instances should be hashable by their fields and used in a dict + # Instances should be hashable by ID, not fields d = {instance: True} - assert d.get(same_data) == d[instance] + assert instance in d + assert d.get(same_data) is None assert d.get(same_pk) is None def test_field_name_not_column_name(self):