.. changelog::
:version: 1.0.0b2
+ .. change::
+ :tags: change, ext, declarative
+ :tickets: 3331
+
+ Loosened some restrictions that were added to ``@declared_attr``
+ objects, such that they were prevented from being called outside
+ of the declarative process; this is related to the enhancements
+ of #3150 which allow ``@declared_attr`` to return a value that is
+ cached based on the current class as it's being configured.
+ The exception raise has been removed, and the behavior changed
+ so that outside of the declarative process, the function decorated by
+ ``@declared_attr`` is called every time just like a regular
+ ``@property``, without using any caching, as none is available
+ at this stage.
+
.. change::
:tags: bug, engine
:tickets: 3330, 3329
"non-mapped class %s" %
(desc.fget.__name__, cls.__name__))
return desc.fget(cls)
- try:
- reg = manager.info['declared_attr_reg']
- except KeyError:
- raise exc.InvalidRequestError(
- "@declared_attr called outside of the "
- "declarative mapping process; is declarative_base() being "
- "used correctly?")
-
- if desc in reg:
+
+ reg = manager.info.get('declared_attr_reg', None)
+
+ if reg is None:
+ return desc.fget(cls)
+ elif desc in reg:
return reg[desc]
else:
reg[desc] = obj = desc.fget(cls)
getattr, Mixin, "my_prop"
)
+ def test_non_decl_access(self):
+ counter = mock.Mock()
+
+ class Mixin(object):
+ @declared_attr
+ def __tablename__(cls):
+ counter(cls)
+ return "foo"
+
+ class Foo(Mixin, Base):
+ id = Column(Integer, primary_key=True)
+
+ @declared_attr
+ def x(cls):
+ cls.__tablename__
+
+ @declared_attr
+ def y(cls):
+ cls.__tablename__
+
+ eq_(
+ counter.mock_calls,
+ [mock.call(Foo)]
+ )
+
+ eq_(Foo.__tablename__, 'foo')
+ eq_(Foo.__tablename__, 'foo')
+
+ eq_(
+ counter.mock_calls,
+ [mock.call(Foo), mock.call(Foo), mock.call(Foo)]
+ )
+
def test_property_noncascade(self):
counter = mock.Mock()