+++ /dev/null
-"""Illustrates a clever technique using Python descriptors to create custom attributes representing SQL expressions when used at the class level, and Python expressions when used at the instance level. In some cases this technique replaces the need to configure the attribute in the mapping, instead relying upon ordinary Python behavior to create custom expression components.
-
-E.g.::
-
- class BaseInterval(object):
- @hybrid
- def contains(self,point):
- return (self.start <= point) & (point < self.end)
-
-"""
+++ /dev/null
-
-import new
-
-class MethodDescriptor(object):
- def __init__(self, func):
- self.func = func
- def __get__(self, instance, owner):
- if instance is None:
- return new.instancemethod(self.func, owner, owner.__class__)
- else:
- return new.instancemethod(self.func, instance, owner)
-
-class PropertyDescriptor(object):
- def __init__(self, fget, fset, fdel):
- self.fget = fget
- self.fset = fset
- self.fdel = fdel
- def __get__(self, instance, owner):
- if instance is None:
- return self.fget(owner)
- else:
- return self.fget(instance)
- def __set__(self, instance, value):
- self.fset(instance, value)
- def __delete__(self, instance):
- self.fdel(instance)
-
-def hybrid(func):
- return MethodDescriptor(func)
-
-def hybrid_property(fget, fset=None, fdel=None):
- return PropertyDescriptor(fget, fset, fdel)
-
-### Example code
-
-from sqlalchemy import MetaData, Table, Column, Integer
-from sqlalchemy.orm import mapper, create_session
-
-metadata = MetaData('sqlite://')
-metadata.bind.echo = True
-
-print "Set up database metadata"
-
-interval_table1 = Table('interval1', metadata,
- Column('id', Integer, primary_key=True),
- Column('start', Integer, nullable=False),
- Column('end', Integer, nullable=False))
-
-interval_table2 = Table('interval2', metadata,
- Column('id', Integer, primary_key=True),
- Column('start', Integer, nullable=False),
- Column('length', Integer, nullable=False))
-
-metadata.create_all()
-
-# A base class for intervals
-
-class BaseInterval(object):
- @hybrid
- def contains(self,point):
- return (self.start <= point) & (point < self.end)
-
- @hybrid
- def intersects(self, other):
- return (self.start < other.end) & (self.end > other.start)
-
- def __repr__(self):
- return "%s(%s..%s)" % (self.__class__.__name__, self.start, self.end)
-
-# Interval stored as endpoints
-
-class Interval1(BaseInterval):
- def __init__(self, start, end):
- self.start = start
- self.end = end
-
- length = hybrid_property(lambda s: s.end - s.start)
-
-mapper(Interval1, interval_table1)
-
-# Interval stored as start and length
-
-class Interval2(BaseInterval):
- def __init__(self, start, length):
- self.start = start
- self.length = length
-
- end = hybrid_property(lambda s: s.start + s.length)
-
-mapper(Interval2, interval_table2)
-
-print "Create the data"
-
-session = create_session()
-
-intervals = [Interval1(1,4), Interval1(3,15), Interval1(11,16)]
-
-for interval in intervals:
- session.add(interval)
- session.add(Interval2(interval.start, interval.length))
-
-session.flush()
-
-print "Clear the cache and do some queries"
-
-session.expunge_all()
-
-for Interval in (Interval1, Interval2):
- print "Querying using interval class %s" % Interval.__name__
-
- print
- print '-- length less than 10'
- print [(i, i.length) for i in session.query(Interval).filter(Interval.length < 10).all()]
-
- print
- print '-- contains 12'
- print session.query(Interval).filter(Interval.contains(12)).all()
-
- print
- print '-- intersects 2..10'
- other = Interval1(2,10)
- result = session.query(Interval).filter(Interval.intersects(other)).order_by(Interval.length).all()
- print [(interval, interval.intersects(other)) for interval in result]
-
--- /dev/null
+"""Define attributes on ORM-mapped classes that have 'hybrid' behavior.
+
+'hybrid' means the attribute has distinct behaviors defined at the
+class level and at the instance level.
+
+Consider a table `interval` as below::
+
+ from sqlalchemy import MetaData, Table, Column, Integer
+ from sqlalchemy.orm import mapper, create_session
+
+ engine = create_engine('sqlite://')
+ metadata = MetaData()
+
+ interval_table = Table('interval', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('start', Integer, nullable=False),
+ Column('end', Integer, nullable=False))
+ metadata.create_all(engine)
+
+We can define higher level functions on mapped classes that produce SQL
+expressions at the class level, and Python expression evaluation at the
+instance level. Below, each function decorated with :func:`hybrid.method`
+or :func:`hybrid.property` may receive ``self`` as an instance of the class,
+or as the class itself::
+
+ # A base class for intervals
+
+ from sqlalchemy.orm import hybrid
+
+ class Interval(object):
+ def __init__(self, start, end):
+ self.start = start
+ self.end = end
+
+ @hybrid.property
+ def length(self):
+ return self.end - self.start
+
+ @hybrid.method
+ def contains(self,point):
+ return (self.start <= point) & (point < self.end)
+
+ @hybrid.method
+ def intersects(self, other):
+ return (self.start < other.end) & (self.end > other.start)
+
+ mapper(Interval1, interval_table1)
+
+ session = sessionmaker(engine)()
+
+ session.add_all(
+ [Interval1(1,4), Interval1(3,15), Interval1(11,16)]
+ )
+ intervals =
+
+ for interval in intervals:
+ session.add(interval)
+ session.add(Interval2(interval.start, interval.length))
+
+ session.commit()
+
+ ### TODO ADD EXAMPLES HERE AND STUFF THIS ISN'T FINISHED ###
+
+"""
+
+class method(object):
+ def __init__(self, func, expr=None):
+ self.func = func
+ self.expr = expr or fund
+
+ def __get__(self, instance, owner):
+ if instance is None:
+ return new.instancemethod(self.expr, owner, owner.__class__)
+ else:
+ return new.instancemethod(self.func, instance, owner)
+
+ def expression(self, expr):
+ self.expr = expr
+ return self
+
+class property(object):
+ def __init__(self, fget, fset=None, fdel=None, expr=None):
+ self.fget = fget
+ self.fset = fset
+ self.fdel = fdel
+ self.expr = expr or fget
+
+ def __get__(self, instance, owner):
+ if instance is None:
+ return self.expr(owner)
+ else:
+ return self.fget(instance)
+
+ def __set__(self, instance, value):
+ self.fset(instance, value)
+
+ def __delete__(self, instance):
+ self.fdel(instance)
+
+ def setter(self, fset):
+ self.fset = fset
+ return self
+
+ def deleter(self, fdel):
+ self.fdel = fdel
+ return self
+
+ def expression(self, expr):
+ self.expr = expr
+ return self
+
return iter(())
- def set_parent(self, parent):
+ def set_parent(self, parent, init):
self.parent = parent
def instrument_class(self, mapper):
# initialize these lazily
ColumnProperty = None
-SynonymProperty = None
-ComparableProperty = None
RelationshipProperty = None
ConcreteInheritedProperty = None
_expire_state = None
for col in col.proxy_set:
self._columntoproperty[col] = prop
- elif isinstance(prop, (ComparableProperty, SynonymProperty)) and \
- setparent:
- if prop.descriptor is None:
- desc = getattr(self.class_, key, None)
- if self._is_userland_descriptor(desc):
- prop.descriptor = desc
- if getattr(prop, 'map_column', False):
- if key not in self.mapped_table.c:
- raise sa_exc.ArgumentError(
- "Can't compile synonym '%s': no column on table "
- "'%s' named '%s'"
- % (prop.name, self.mapped_table.description, key))
- elif self.mapped_table.c[key] in self._columntoproperty and \
- self._columntoproperty[
- self.mapped_table.c[key]
- ].key == prop.name:
- raise sa_exc.ArgumentError(
- "Can't call map_column=True for synonym %r=%r, "
- "a ColumnProperty already exists keyed to the name "
- "%r for column %r" %
- (key, prop.name, prop.name, key)
- )
- p = ColumnProperty(self.mapped_table.c[key])
- self._configure_property(
- prop.name, p,
- init=init,
- setparent=setparent)
- p._mapped_by_synonym = key
-
+ prop.key = key
+
+ if setparent:
+ prop.set_parent(self, init)
+
if key in self._props and \
getattr(self._props[key], '_mapped_by_synonym', False):
syn = self._props[key]._mapped_by_synonym
)
self._props[key] = prop
- prop.key = key
-
- if setparent:
- prop.set_parent(self)
if not self.non_primary:
prop.instrument_class(self)
def create_row_processor(self, selectcontext, path, mapper, row, adapter):
return (None, None)
-
+
+ def set_parent(self, parent, init):
+ if self.descriptor is None:
+ desc = getattr(parent.class_, self.key, None)
+ if parent._is_userland_descriptor(desc):
+ self.descriptor = desc
+ if self.map_column:
+ if self.key not in parent.mapped_table.c:
+ raise sa_exc.ArgumentError(
+ "Can't compile synonym '%s': no column on table "
+ "'%s' named '%s'"
+ % (self.name, parent.mapped_table.description, self.key))
+ elif parent.mapped_table.c[self.key] in \
+ parent._columntoproperty and \
+ parent._columntoproperty[
+ parent.mapped_table.c[self.key]
+ ].key == self.name:
+ raise sa_exc.ArgumentError(
+ "Can't call map_column=True for synonym %r=%r, "
+ "a ColumnProperty already exists keyed to the name "
+ "%r for column %r" %
+ (self.key, self.name, self.name, self.key)
+ )
+ p = ColumnProperty(parent.mapped_table.c[self.key])
+ parent._configure_property(
+ self.name, p,
+ init=init,
+ setparent=True)
+ p._mapped_by_synonym = self.key
+
+ self.parent = parent
+
def instrument_class(self, mapper):
class_ = self.parent.class_
self.doc = doc or (descriptor and descriptor.__doc__) or None
util.set_creation_order(self)
+ def set_parent(self, parent, init):
+ if self.descriptor is None:
+ desc = getattr(parent.class_, self.key, None)
+ if parent._is_userland_descriptor(desc):
+ self.descriptor = desc
+ self.parent = parent
+
def instrument_class(self, mapper):
"""Set up a proxy to the unmanaged descriptor."""
log.class_logger(RelationshipProperty)
mapper.ColumnProperty = ColumnProperty
-mapper.SynonymProperty = SynonymProperty
-mapper.ComparableProperty = ComparableProperty
mapper.RelationshipProperty = RelationshipProperty
mapper.ConcreteInheritedProperty = ConcreteInheritedProperty