]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add hybrid extension
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 6 Aug 2010 18:29:21 +0000 (14:29 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 6 Aug 2010 18:29:21 +0000 (14:29 -0400)
- mapper begins to lose awareness of SynonynmProperty, ComparableProperty

examples/derived_attributes/__init__.py [deleted file]
examples/derived_attributes/attributes.py [deleted file]
lib/sqlalchemy/ext/hybrid.py [new file with mode: 0644]
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/properties.py

diff --git a/examples/derived_attributes/__init__.py b/examples/derived_attributes/__init__.py
deleted file mode 100644 (file)
index 98c946f..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-"""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)
-
-"""
diff --git a/examples/derived_attributes/attributes.py b/examples/derived_attributes/attributes.py
deleted file mode 100644 (file)
index 15af7b2..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-
-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]
-
diff --git a/lib/sqlalchemy/ext/hybrid.py b/lib/sqlalchemy/ext/hybrid.py
new file mode 100644 (file)
index 0000000..2432ec9
--- /dev/null
@@ -0,0 +1,111 @@
+"""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
+    
index 40531697696b486e0b2c0ab3664d88467150e236..f0906ec54fcda724ed4ac0c436b874a8dce7205a 100644 (file)
@@ -474,7 +474,7 @@ class MapperProperty(object):
 
         return iter(())
 
-    def set_parent(self, parent):
+    def set_parent(self, parent, init):
         self.parent = parent
 
     def instrument_class(self, mapper):
index 7c2f550eb7eb853ffd52c64391380dd7428cf3b7..54674a7cc12aba2a0de17fba7f1d9d60c2336166 100644 (file)
@@ -57,8 +57,6 @@ _COMPILE_MUTEX = util.threading.RLock()
 
 # initialize these lazily
 ColumnProperty = None
-SynonymProperty = None
-ComparableProperty = None
 RelationshipProperty = None
 ConcreteInheritedProperty = None
 _expire_state = None
@@ -709,35 +707,11 @@ class Mapper(object):
                 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
@@ -748,10 +722,6 @@ class Mapper(object):
                     )
             
         self._props[key] = prop
-        prop.key = key
-
-        if setparent:
-            prop.set_parent(self)
 
         if not self.non_primary:
             prop.instrument_class(self)
index 53503862bb624d61650035642889b1464a3d126a..9cc03833a25dee63d01de0fea8d0699053ad00dc 100644 (file)
@@ -318,7 +318,38 @@ class SynonymProperty(MapperProperty):
 
     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_
 
@@ -372,6 +403,13 @@ class ComparableProperty(MapperProperty):
         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."""
 
@@ -1414,7 +1452,5 @@ PropertyLoader = RelationProperty = RelationshipProperty
 log.class_logger(RelationshipProperty)
 
 mapper.ColumnProperty = ColumnProperty
-mapper.SynonymProperty = SynonymProperty
-mapper.ComparableProperty = ComparableProperty
 mapper.RelationshipProperty = RelationshipProperty
 mapper.ConcreteInheritedProperty = ConcreteInheritedProperty