]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Declarative gains @synonym_for and @comparable_using decorators
authorJason Kirtland <jek@discorporate.us>
Mon, 17 Mar 2008 22:55:43 +0000 (22:55 +0000)
committerJason Kirtland <jek@discorporate.us>
Mon, 17 Mar 2008 22:55:43 +0000 (22:55 +0000)
CHANGES
doc/build/content/plugins.txt
lib/sqlalchemy/ext/declarative.py
test/ext/declarative.py

diff --git a/CHANGES b/CHANGES
index 41d18462fc025c9d6e24a026e5d102fdb72f2c5a..695c73035336a4587be464e64f7710aba3d4afbb 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -30,6 +30,10 @@ CHANGES
       "instrument" keyword argument, e.g.: somekey =
       synonym('_somekey', instrument=property(g, s))
 
+    - Declarative also gained @synonym_for(...) and
+      @comparable_using(...), front-ends for synonym and
+      comparable_property.
+
 0.4.4
 ------
 - sql
index 8c1d3a55bd26bb8a312ba9c4e544e65a9f0d13a2..0de25e2b79decf9751acb11ec8a8497c30d0ec3b 100644 (file)
@@ -102,7 +102,33 @@ The above synonym is then usable as an instance attribute as well as a class-lev
     x = MyClass()
     x.attr = "some value"
     session.query(MyClass).filter(MyClass.attr == 'some other value').all()
+
+The `synonyn_for` decorator can accomplish the same task:
+
+    {python}
+    class MyClass(Base):
+        __tablename__ = 'sometable'
         
+        _attr = Column('attr', String)
+
+        @synonyn_for('_attr')
+        @property
+        def attr(self):
+            return self._some_attr
+
+Similarly, `comparable_using` is a front end for the `comparable_property` ORM function:
+
+    {python}
+    class MyClass(Base):
+        __tablename__ = 'sometable'
+
+        name = Column('name', String)
+
+        @comparable_using(MyUpperCaseComparator)
+        @property
+        def uc_name(self):
+            return self.name.upper()
+
 As an alternative to `__tablename__`, a direct `Table` construct may be used:
 
     {python}
index 41a51c8c698b879c4ddfa4623b46d40a46d98931..43cdad1cc69a3bdcad53b45eb3024aea2ab0d748 100644 (file)
@@ -143,12 +143,13 @@ Mapped instances then make usage of ``Session`` in the usual way.
 
 """
 from sqlalchemy.schema import Table, SchemaItem, Column, MetaData
-from sqlalchemy.orm import synonym as _orm_synonym, mapper
+from sqlalchemy.orm import synonym as _orm_synonym, mapper, comparable_property
 from sqlalchemy.orm.interfaces import MapperProperty
 from sqlalchemy.orm.properties import PropertyLoader
 from sqlalchemy import util
 
-__all__ = ['declarative_base', 'declared_synonym']
+__all__ = ['declarative_base', 'synonym_for', 'comparable_using',
+           'declared_synonym']
 
 class DeclarativeMeta(type):
     def __init__(cls, classname, bases, dict_):
@@ -218,7 +219,50 @@ def declared_synonym(prop, name):
     
     return _orm_synonym(name, instrument=prop)
 declared_synonym = util.deprecated(declared_synonym)
-        
+
+def synonym_for(name, map_column=False):
+    """Decorator, make a Python @property a query synonym for a column.
+    
+    A decorator version of [sqlalchemy.orm#synonym()].  The function being
+    decoratred is the 'instrument', otherwise passes its arguments through
+    to synonym().
+
+      @synonym_for('col')
+      @property
+      def prop(self):
+          return 'special sauce'
+
+    The regular ``synonym()`` is also usable directly in a declarative
+    setting and may be convenient for read/write properties::
+    
+      prop = synonym('col', instrument=property(_read_prop, _write_prop))
+
+    """
+    def decorate(fn):
+        return _orm_synonym(name, map_column=map_column, instrument=fn)
+    return decorate
+
+
+def comparable_using(comparator_factory):
+    """Decorator, allow a Python @property to be used in query criteria.
+
+    A decorator front end to [sqlalchemy.orm#comparable_property()], passes
+    throgh the comparator_factory and the function being decorated.
+
+      @comparable_using(MyComparatorType)
+      @property
+      def prop(self):
+          return 'special sauce'
+
+    The regular ``comparable_property()`` is also usable directly in a
+    declarative setting and may be convenient for read/write properties::
+
+      prop = comparable_property(MyComparatorType)
+    """
+    def decorate(fn):
+        return comparable_property(comparator_factory, fn)
+    return decorate
+
 def declarative_base(engine=None, metadata=None):
     lcl_metadata = metadata or MetaData()
     class Base(object):
index fcb0125ac89757dcce64bf82d5dd351b0c79dd25..d59f88d0870a6d6045a20754cdf72a39c3033422 100644 (file)
@@ -2,7 +2,8 @@ import testenv; testenv.configure_for_tests()
 
 from sqlalchemy import *
 from sqlalchemy.orm import *
-from sqlalchemy.ext.declarative import declarative_base, declared_synonym
+from sqlalchemy.ext.declarative import declarative_base, declared_synonym, \
+                                       synonym_for, comparable_using
 from sqlalchemy import exceptions
 from testlib.fixtures import Base as Fixture
 from testlib import *
@@ -459,5 +460,68 @@ class DeclarativeReflectionTest(TestBase):
         self.assertEquals(a1, IMHandle(network='lol', handle='zomg'))
         self.assertEquals(a1.user, User(name='u1'))
 
+    def test_synonym_for(self):
+        class User(Base, Fixture):
+            __tablename__ = 'users'
+
+            id = Column('id', Integer, primary_key=True)
+            name = Column('name', String(50))
+
+            @synonym_for('name')
+            @property
+            def namesyn(self):
+                return self.name
+
+        Base.metadata.create_all()
+
+        sess = create_session()
+        u1 = User(name='someuser')
+        assert u1.name == "someuser", u1.name
+        assert u1.namesyn == 'someuser', u1.namesyn
+        sess.save(u1)
+        sess.flush()
+
+        rt = sess.query(User).filter(User.namesyn=='someuser').one()
+        self.assertEquals(rt, u1)
+
+    def test_comparable_using(self):
+        class NameComparator(PropComparator):
+            @property
+            def upperself(self):
+                cls = self.prop.parent.class_
+                col = getattr(cls, 'name')
+                return func.upper(col)
+
+            def operate(self, op, other, **kw):
+                return op(self.upperself, other, **kw)
+
+        class User(Base, Fixture):
+            __tablename__ = 'users'
+
+            id = Column('id', Integer, primary_key=True)
+            name = Column('name', String(50))
+
+            @comparable_using(NameComparator)
+            @property
+            def uc_name(self):
+                return self.name is not None and self.name.upper() or None
+
+        Base.metadata.create_all()
+
+        sess = create_session()
+        u1 = User(name='someuser')
+        assert u1.name == "someuser", u1.name
+        assert u1.uc_name == 'SOMEUSER', u1.uc_name
+        sess.save(u1)
+        sess.flush()
+        sess.clear()
+
+        rt = sess.query(User).filter(User.uc_name=='SOMEUSER').one()
+        self.assertEquals(rt, u1)
+        sess.clear()
+
+        rt = sess.query(User).filter(User.uc_name.startswith('SOMEUSE')).one()
+        self.assertEquals(rt, u1)
+
 if __name__ == '__main__':
     testing.main()