From: Mike Bayer Date: Tue, 2 Jul 2013 22:02:20 +0000 (-0400) Subject: ORM descriptors such as hybrid properties can now be referenced X-Git-Tag: rel_0_8_2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=761414a07be34aaebf76e7e35707c2d8d8772bb8;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git ORM descriptors such as hybrid properties can now be referenced by name in a string argument used with ``order_by``, ``primaryjoin``, or similar in :func:`.relationship`, in addition to column-bound attributes. [ticket:2761] --- diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index 48f57e8cdb..d25cb8d676 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -6,6 +6,15 @@ .. changelog:: :version: 0.8.2 + .. change:: + :tags: feature, orm, declarative + :tickets: 2761 + + ORM descriptors such as hybrid properties can now be referenced + by name in a string argument used with ``order_by``, + ``primaryjoin``, or similar in :func:`.relationship`, + in addition to column-bound attributes. + .. change:: :tags: feature, firebird :tickets: 2763 diff --git a/lib/sqlalchemy/ext/declarative/clsregistry.py b/lib/sqlalchemy/ext/declarative/clsregistry.py index 89975716de..ab2b5ad490 100644 --- a/lib/sqlalchemy/ext/declarative/clsregistry.py +++ b/lib/sqlalchemy/ext/declarative/clsregistry.py @@ -12,7 +12,7 @@ This system allows specification of classes and expressions used in from ...orm.properties import ColumnProperty, RelationshipProperty, \ SynonymProperty from ...schema import _get_table_key -from ...orm import class_mapper +from ...orm import class_mapper, interfaces from ... import util from ... import exc import weakref @@ -190,19 +190,21 @@ class _GetColumns(object): def __getattr__(self, key): mp = class_mapper(self.cls, configure=False) if mp: - if not mp.has_property(key): + if key not in mp.all_orm_descriptors: raise exc.InvalidRequestError( "Class %r does not have a mapped column named %r" % (self.cls, key)) - prop = mp.get_property(key) - if isinstance(prop, SynonymProperty): - key = prop.name - elif not isinstance(prop, ColumnProperty): - raise exc.InvalidRequestError( - "Property %r is not an instance of" - " ColumnProperty (i.e. does not correspond" - " directly to a Column)." % key) + desc = mp.all_orm_descriptors[key] + if desc.extension_type is interfaces.NOT_EXTENSION: + prop = desc.property + if isinstance(prop, SynonymProperty): + key = prop.name + elif not isinstance(prop, ColumnProperty): + raise exc.InvalidRequestError( + "Property %r is not an instance of" + " ColumnProperty (i.e. does not correspond" + " directly to a Column)." % key) return getattr(self.cls, key) diff --git a/test/ext/declarative/test_basic.py b/test/ext/declarative/test_basic.py index 0fe54a1549..4b5736c9f2 100644 --- a/test/ext/declarative/test_basic.py +++ b/test/ext/declarative/test_basic.py @@ -21,7 +21,10 @@ from sqlalchemy.testing.util import gc_collect Base = None -class DeclarativeTestBase(fixtures.TestBase, testing.AssertsExecutionResults): +class DeclarativeTestBase(fixtures.TestBase, + testing.AssertsExecutionResults, + testing.AssertsCompiledSQL): + __dialect__ = 'default' def setup(self): global Base Base = decl.declarative_base(testing.db) @@ -290,7 +293,39 @@ class DeclarativeTest(DeclarativeTestBase): foo.rel = u1 assert foo.rel == u1 - def test_string_dependency_resolution_two(self): + def test_string_dependency_resolution_orm_descriptor(self): + from sqlalchemy.ext.hybrid import hybrid_property + + class User(Base): + __tablename__ = 'user' + id = Column(Integer, primary_key=True) + firstname = Column(String(50)) + lastname = Column(String(50)) + game_id = Column(Integer, ForeignKey('game.id')) + + @hybrid_property + def fullname(self): + return self.firstname + " " + self.lastname + + class Game(Base): + __tablename__ = 'game' + id = Column(Integer, primary_key=True) + name = Column(String(50)) + users = relationship("User", order_by="User.fullname") + + s = Session() + self.assert_compile( + s.query(Game).options(joinedload(Game.users)), + "SELECT game.id AS game_id, game.name AS game_name, " + "user_1.id AS user_1_id, user_1.firstname AS user_1_firstname, " + "user_1.lastname AS user_1_lastname, " + "user_1.game_id AS user_1_game_id " + "FROM game LEFT OUTER JOIN \"user\" AS user_1 ON game.id = " + "user_1.game_id ORDER BY " + "user_1.firstname || :firstname_1 || user_1.lastname" + ) + + def test_string_dependency_resolution_no_table(self): class User(Base, fixtures.ComparableEntity): __tablename__ = 'users'