From cd076470baf2fce0eebf5853e3145d96a9d48378 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 18 Mar 2015 18:57:13 -0400 Subject: [PATCH] - 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. fixes #3331 --- doc/build/changelog/changelog_10.rst | 15 ++++++++++++ lib/sqlalchemy/ext/declarative/api.py | 15 +++++------- test/ext/declarative/test_mixin.py | 33 +++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 6d8aa67da8..70f31f5e65 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -18,6 +18,21 @@ .. 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 diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py index 048533b92c..713ea0aba3 100644 --- a/lib/sqlalchemy/ext/declarative/api.py +++ b/lib/sqlalchemy/ext/declarative/api.py @@ -175,15 +175,12 @@ class declared_attr(interfaces._MappedAttribute, property): "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) diff --git a/test/ext/declarative/test_mixin.py b/test/ext/declarative/test_mixin.py index 5cefe8d476..45b8816710 100644 --- a/test/ext/declarative/test_mixin.py +++ b/test/ext/declarative/test_mixin.py @@ -1392,6 +1392,39 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL): 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() -- 2.47.3