Author: luke
Date: 2007-04-24 09:45:32 -0600 (Tue, 24 Apr 2007)
New Revision: 2591
Added:
SQLObject/branches/cache-deps-on-views/sqlobject/tests/test_derived_accessors.py
Modified:
SQLObject/branches/cache-deps-on-views/sqlobject/__init__.py
SQLObject/branches/cache-deps-on-views/sqlobject/col.py
SQLObject/branches/cache-deps-on-views/sqlobject/main.py
SQLObject/branches/cache-deps-on-views/sqlobject/sqlbuilder.py
Log:
Derived Columns
Modified: SQLObject/branches/cache-deps-on-views/sqlobject/__init__.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/__init__.py 2007-04-24 15:07:03 UTC (rev 2590)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/__init__.py 2007-04-24 15:45:32 UTC (rev 2591)
@@ -4,6 +4,7 @@
from styles import *
from joins import *
from index import *
+from derived import *
from dbconnection import connectionForURI
import dberrors
Modified: SQLObject/branches/cache-deps-on-views/sqlobject/col.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/col.py 2007-04-24 15:07:03 UTC (rev 2590)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/col.py 2007-04-24 15:45:32 UTC (rev 2591)
@@ -131,6 +131,7 @@
title=None,
tags=[],
origName=None,
+ derived=False,
extra_vars=None):
super(SOCol, self).__init__()
@@ -150,6 +151,7 @@
self.creationOrder = creationOrder
self.immutable = immutable
+ self.derived = derived
# cascade can be one of:
# None: no constraint is generated
Modified: SQLObject/branches/cache-deps-on-views/sqlobject/main.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/main.py 2007-04-24 15:07:03 UTC (rev 2590)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/main.py 2007-04-24 15:45:32 UTC (rev 2591)
@@ -293,6 +293,8 @@
# None of these objects can be shared with superclasses
cls.columns = {}
cls.columnList = []
+ cls.columnsExtended= {}
+ cls.columnListExtended = []
# These, however, can be shared:
cls.columnDefinitions = cls.columnDefinitions.copy()
cls.indexes = []
@@ -339,9 +341,13 @@
"add the column %r"
% (soClass.__module__, soClass.__name__, name, name))
sqlmeta.columnDefinitions[name] = columnDef
- sqlmeta.columns[name] = column
+ sqlmeta.columnsExtended[name] = column
# A stable-ordered version of the list...
- sqlmeta.columnList.append(column)
+ sqlmeta.columnListExtended.append(column)
+
+ if not column.derived:
+ sqlmeta.columns[name] = column
+ sqlmeta.columnList.append(column)
###################################################
# Create the getter function(s). We'll start by
@@ -350,7 +356,9 @@
# we'll alias that to _SO_get_columnName. This
# allows a sort of super call, even though there's
# no superclass that defines the database access.
- if sqlmeta.cacheValues:
+ if column.derived:
+ getter = column.__get__
+ elif sqlmeta.cacheValues:
# We create a method here, which is just a function
# that takes "self" as the first argument.
getter = eval('lambda self: self._SO_loadValue(%s)' % repr(instanceName(name)))
@@ -475,9 +483,12 @@
cls.send(events.DeleteColumnSignal, connection, column.name, column,
post_funcs)
name = column.name
- del sqlmeta.columns[name]
+ del sqlmeta.columnsExtended[name]
del sqlmeta.columnDefinitions[name]
- sqlmeta.columnList.remove(column)
+ sqlmeta.columnListExtended.remove(column)
+ if not column.derived:
+ del sqlmeta.columns[name]
+ sqlmeta.columnList.remove(column)
delattr(soClass, rawGetterName(name))
if sqlmeta._plainGetters.has_key(name):
delattr(soClass, getterName(name))
Modified: SQLObject/branches/cache-deps-on-views/sqlobject/sqlbuilder.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/sqlbuilder.py 2007-04-24 15:07:03 UTC (rev 2590)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/sqlbuilder.py 2007-04-24 15:45:32 UTC (rev 2591)
@@ -1201,9 +1201,10 @@
return True
def __getattr__(self, attr):
- if self.soClass is None:
+ try:
+ return getattr(self.soClass.q, attr)
+ except AttributeError:
return _Delay(self, attr)
- return getattr(self.soClass.q, attr)
class _Delay(SQLExpression):
def __init__(self, proxy, attr):
Added: SQLObject/branches/cache-deps-on-views/sqlobject/tests/test_derived_accessors.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/tests/test_derived_accessors.py (rev 0)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/tests/test_derived_accessors.py 2007-04-24 15:45:32 UTC (rev 2591)
@@ -0,0 +1,71 @@
+from sqlobject import *
+from sqlobject.sqlbuilder import ImportProxy
+from sqlobject.tests.dbtest import *
+
+class DerivePerson(SQLObject):
+ name = StringCol(alternateID=True)
+ city = StringCol()
+ phones = SQLMultipleJoin('DerivePhone', joinColumn='personID')
+
+ primaryPhone = OneFrom('phones', query=ImportProxy('DerivePhone').q.isPrimary==True)
+ secondaryPhones = FilterFrom('phones', query=ImportProxy('DerivePhone').q.isPrimary==False)
+
+ primaryMinutes = ValueFrom('primaryPhone', ImportProxy('DerivePhone').q.totalMinutes)
+ mostMinutes = MaxFrom('phones', ImportProxy('DerivePhone').q.totalMinutes)
+ mostSecondaryMinutes = MaxFrom('secondaryPhones', ImportProxy('DerivePhone').q.totalMinutes)
+# mostUsedPhone = OneFrom('phones', query=ImportProxy('DerivePhone').q.totalMinutes==SelfProxy.q.mostMinutes)
+
+
+class DerivePhone(SQLObject):
+ number = StringCol(alternateID=True)
+ isPrimary = BoolCol()
+
+ city = ValueFrom('person', ImportProxy('Person').q.city)
+ totalMinutes = SumFrom('calls', ImportProxy('DerivePhoneCall').q.minutes)
+# totalLocalMinutes = SumFrom('calls', ImportProxy('DerivedPhoneCall').q.minutes,
+# query=ImportProxy('DerivedPhoneCall').q.city == SelfProxy.q.city)
+
+ person = ForeignKey('DerivePerson')
+ calls = SQLMultipleJoin('DerivePhoneCall', joinColumn='phoneID')
+
+class DerivePhoneCall(SQLObject):
+ #onDate = DateTimeCol(default="now")
+ minutes = IntCol()
+ city = StringCol()
+
+ phone = SQLForeignKey('DerivePhone')
+
+
+def setup_module(mod):
+ setupClass([DerivePerson, DerivePhone, DerivePhoneCall], force=True)
+ ppl = inserts(DerivePerson, [{'name':'Luke', 'city':'Chicago'}])
+ phones = inserts(DerivePhone, [
+ {'number':'1234', 'person':ppl[0], 'isPrimary':True},
+ {'number':'5678', 'person':ppl[0], 'isPrimary':False},
+ {'number':'7890', 'person':ppl[0], 'isPrimary':False}
+ ])
+ calls = inserts(DerivePhoneCall, [
+ {'minutes': 10, 'city':'Chicago', 'phone':phones[0]},
+ {'minutes': 20, 'city':'Detroit', 'phone':phones[0]},
+ {'minutes': 10, 'city':'Chicago', 'phone':phones[1]},
+ {'minutes': 15, 'city':'Chicago', 'phone':phones[1]},
+ ])
+
+
+def test_oneFrom():
+ luke = DerivePerson.byName('Luke')
+ ph = DerivePhone.byNumber('1234')
+ assert luke.primaryPhone == ph
+ assert luke.primaryPhone == luke.phones.filter(DerivePhone.q.isPrimary==True).getOne()
+ #assert luke.secondaryPhones == phones[1]
+
+def test_filterFrom():
+ luke = DerivePerson.byName('Luke')
+ assert list(luke.secondaryPhones) == list(luke.phones.filter(DerivePhone.q.isPrimary==False))
+
+def test_aggregates():
+ ph1 = DerivePhone.byNumber('1234')
+ ph2 = DerivePhone.byNumber('5678')
+ assert ph1.totalMinutes == ph1.calls.sum(DerivePhoneCall.q.minutes)
+ #assert ph1.totalLocalMinutes == ph1.calls.filter(DerivePhone.sum(DerivePhoneCall.q.minutes)
+
|