.. 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
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
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)
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)
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'