.. _change_3653:
-Hybrid properties and methods now propagate the docstring
----------------------------------------------------------
+Hybrid properties and methods now propagate the docstring as well as .info
+--------------------------------------------------------------------------
A hybrid method or property will now reflect the ``__doc__`` value
present in the original docstring::
recipe, however we'll be looking to see that no other regressions occur for
users.
+As part of this change, the :attr:`.hybrid_property.info` collection is now
+also propagated from the hybrid descriptor itself, rather than from the underlying
+expression. That is, accessing ``A.some_name.info`` now returns the same
+dictionary that you'd get from ``inspect(A).all_orm_descriptors['some_name'].info``::
+
+ >>> A.some_name.info['foo'] = 'bar'
+ >>> from sqlalchemy import inspect
+ >>> inspect(A).all_orm_descriptors['some_name'].info
+ {'foo': 'bar'}
+
+Note that this ``.info`` dictionary is **separate** from that of a mapped attribute
+which the hybrid descriptor may be proxying directly; this is a behavioral
+change from 1.0. The wrapper will still proxy other useful attributes
+of a mirrored attribute such as :attr:`.QueryableAttribute.property` and
+:attr:`.QueryableAttribute.class_`.
+
:ticket:`3653`
.. _change_3601:
producing method."""
def _expr(cls):
- return ExprComparator(expr(cls))
+ return ExprComparator(expr(cls), self)
util.update_wrapper(_expr, expr)
self.expr = _expr
class ExprComparator(Comparator):
+ def __init__(self, expression, hybrid):
+ self.expression = expression
+ self.hybrid = hybrid
def __getattr__(self, key):
return getattr(self.expression, key)
+ @property
+ def info(self):
+ return self.hybrid.info
+
+ @property
+ def property(self):
+ return self.expression.property
+
def operate(self, op, *other, **kwargs):
return op(self.expression, *other, **kwargs)
eq_(a1._value, 10)
+class PropertyMirrorTest(fixtures.TestBase, AssertsCompiledSQL):
+ __dialect__ = 'default'
+
+ def _fixture(self):
+ Base = declarative_base()
+
+ class A(Base):
+ __tablename__ = 'a'
+ id = Column(Integer, primary_key=True)
+ _value = Column("value", String)
+
+ @hybrid.hybrid_property
+ def value(self):
+ "This is an instance-level docstring"
+ return self._value
+ return A
+
+ def test_property(self):
+ A = self._fixture()
+
+ is_(A.value.property, A._value.property)
+
+ def test_key(self):
+ A = self._fixture()
+ eq_(A.value.key, "value")
+ eq_(A._value.key, "_value")
+
+ def test_class(self):
+ A = self._fixture()
+ is_(A.value.class_, A._value.class_)
+
+ def test_get_history(self):
+ A = self._fixture()
+ inst = A(_value=5)
+ eq_(A.value.get_history(inst), A._value.get_history(inst))
+
+ def test_info_not_mirrored(self):
+ A = self._fixture()
+ A._value.info['foo'] = 'bar'
+ A.value.info['bar'] = 'hoho'
+
+ eq_(A._value.info, {'foo': 'bar'})
+ eq_(A.value.info, {'bar': 'hoho'})
+
+ def test_info_from_hybrid(self):
+ A = self._fixture()
+ A._value.info['foo'] = 'bar'
+ A.value.info['bar'] = 'hoho'
+
+ insp = inspect(A)
+ is_(insp.all_orm_descriptors['value'].info, A.value.info)
+
+
class MethodExpressionTest(fixtures.TestBase, AssertsCompiledSQL):
__dialect__ = 'default'