From: Jason Kirtland Date: Mon, 17 Mar 2008 22:55:43 +0000 (+0000) Subject: - Declarative gains @synonym_for and @comparable_using decorators X-Git-Tag: rel_0_4_5~85 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=22197ca9c51173300450902887f8e6f8b9680007;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Declarative gains @synonym_for and @comparable_using decorators --- diff --git a/CHANGES b/CHANGES index 41d18462fc..695c730353 100644 --- 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 diff --git a/doc/build/content/plugins.txt b/doc/build/content/plugins.txt index 8c1d3a55bd..0de25e2b79 100644 --- a/doc/build/content/plugins.txt +++ b/doc/build/content/plugins.txt @@ -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} diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 41a51c8c69..43cdad1cc6 100644 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -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): diff --git a/test/ext/declarative.py b/test/ext/declarative.py index fcb0125ac8..d59f88d087 100644 --- a/test/ext/declarative.py +++ b/test/ext/declarative.py @@ -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()