Author: luke
Date: 2007-04-14 15:51:58 -0600 (Sat, 14 Apr 2007)
New Revision: 2529
Added:
SQLObject/branches/cache-deps-on-views/sqlobject/materialized/
SQLObject/branches/cache-deps-on-views/sqlobject/materialized/__init__.py
SQLObject/branches/cache-deps-on-views/sqlobject/materialized/dependency.py
SQLObject/branches/cache-deps-on-views/sqlobject/materialized/materialized.py
SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/
SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/__init__.py
SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_dependency.py
SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_dependency_manager.py
SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_materialized.py
Modified:
SQLObject/branches/cache-deps-on-views/sqlobject/events.py
SQLObject/branches/cache-deps-on-views/sqlobject/main.py
Log:
Non-functional start
Modified: SQLObject/branches/cache-deps-on-views/sqlobject/events.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/events.py 2007-04-14 07:48:21 UTC (rev 2528)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/events.py 2007-04-14 21:51:58 UTC (rev 2529)
@@ -138,6 +138,16 @@
post_funcs are called with ``(cls, so_column_obj)``
"""
+class AddJoinSignal(Signal):
+ """
+ Called when a join is added to a class, with arguments
+ ``(cls, join_name, join_definition, post_funcs)``.
+ This is called *after* the join has been added,
+ and is called for each join after class creation.
+
+ post_funcs are called with ``(cls, so_join_obj)``
+ """
+
# @@: Signals for indexes and joins? These are mostly event consumers,
# though.
Modified: SQLObject/branches/cache-deps-on-views/sqlobject/main.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/main.py 2007-04-14 07:48:21 UTC (rev 2528)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/main.py 2007-04-14 21:51:58 UTC (rev 2529)
@@ -510,6 +510,9 @@
########################################
def addJoin(cls, joinDef):
+ post_funcs = []
+ cls.send(events.AddJoinSignal, cls.soClass,
+ joinDef.name, joinDef, post_funcs)
sqlmeta = cls
soClass = cls.soClass
# The name of the method we'll create. If it's
@@ -556,6 +559,9 @@
if soClass._SO_finishedClassCreation:
makeProperties(soClass)
+
+ for func in post_funcs:
+ func(soClass, join)
addJoin = classmethod(addJoin)
Property changes on: SQLObject/branches/cache-deps-on-views/sqlobject/materialized
___________________________________________________________________
Name: svn:ignore
+ *.pyc
Added: SQLObject/branches/cache-deps-on-views/sqlobject/materialized/__init__.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/materialized/__init__.py (rev 0)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/materialized/__init__.py 2007-04-14 21:51:58 UTC (rev 2529)
@@ -0,0 +1,2 @@
+from dependency import *
+from materialized import *
\ No newline at end of file
Added: SQLObject/branches/cache-deps-on-views/sqlobject/materialized/dependency.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/materialized/dependency.py (rev 0)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/materialized/dependency.py 2007-04-14 21:51:58 UTC (rev 2529)
@@ -0,0 +1,134 @@
+from sqlobject import declarative
+from sqlobject.events import *
+from sqlobject.include.pydispatch import dispatcher
+
+from operator import isCallable
+
+def _processDependentOn(new_class_name, bases, new_attrs, post_funcs, early_funcs):
+ def f(cls):
+ for name, value in new_attrs.items():
+ if hasattr(value, '_dependentOn'):
+ for dep, maker, source in value._dependentOn:
+ dep.add(maker(cls, name, value),source)
+ early_funcs.append(f)
+
+listen(_processDependentOn, dispatcher.Any, ClassCreateSignal)
+
+class depFromClsAttr(object):
+ def __call__(self, cls, name, value):
+ return (cls.__name__, name)
+
+
+class DependencyItem(object):
+ def __init__(self, item):
+ self.key = item
+ self.value = item
+
+class Dependency(object):
+ DependencyItemClass = DependencyItem
+ DecoratorFactory = depFromClsAttr
+
+ def __init__(self, target, source, **extra):
+ self.target = self.DependencyItemClass(target)
+ self.source = self.DependencyItemClass(source)
+
+class DependencyManager(object):
+ DependencyClass = Dependency
+
+ def __init__(self):
+ self._deps = {}
+ self._reverseDeps = {}
+
+ def add(self, *args):
+ ''' Add a dependency. '''
+ dep = self.dependencyForAdd(*args)
+ self._deps.setdefault(dep.source.key, set()).add(dep.target)
+ self._reverseDeps.setdefault(dep.target.key, set()).add(dep.source)
+
+ def get(self, source):
+ ''' get immediate dependent values for source '''
+ return self.getMany([source])
+
+ def getMany(self, sources):
+ deps = (self.treeFrom(source, depth=1) for source in sources)
+ return reduce(set.union, (set(y[0] for y in x.values()) for x in deps), set())
+
+ def treeFrom(self, source, depth=None):
+ ''' dictionary of dependencies with their dependenices arising from source'''
+ return self._tree(self._deps, source, depth=depth)
+
+ def treeTo(self, source, depth=None):
+ ''' dictionary of dependencies with their dependenices leading to source'''
+ return self._tree(self._reverseDeps, source, depth=depth)
+
+ def _tree(self, deps, source, depth=None):
+ if depth == 0:
+ return {}
+ if depth:
+ depth = depth-1
+
+ return dict(((target.key, (target.value, self._tree(deps, target.key, depth=depth)))
+ for target in deps.get(source,set())))
+
+
+ def dependentOn(self, source, factory=None):
+ ''' decorator '''
+ if factory is None:
+ factory = self.DependencyClass.DecoratorFactory()
+ def decorate(func):
+ depOn = getattr(func, '_dependentOn', [])
+ depOn.append((self, factory, source))
+ setattr(func, '_dependentOn', depOn)
+ return func
+ return decorate
+
+
+ def dependencyForAdd(self, *args):
+ if len(args) == 1 and isinstance(args[0], self.DependencyClass):
+ dep = args[0]
+ elif len(args) == 2:
+ dep = self.DependencyClass(*args)
+ else:
+ raise TypeError('Incorrect arguments to add(), expected %s instance or (target,source), got: %s' % (self.DependencyClass.__name__, args))
+ return dep
+
+#if isinstance(inst, str):
+# sourceClass = inst
+#else:
+# sourceClass = inst.__class__.__name__
+
+class SQLDependency(Dependency):
+ pass
+
+class SQLDependencyManager(DependencyManager):
+
+ def process(self, inst, attrs):
+ ''' find and recalculate items dependent on attrs'''
+ sourceClass = inst.__class__.__name__
+ deps = self.getMany((sourceClass, attr) for attr in attrs)
+ for targetClass, targetAttr, route in deps:
+ targetClass = getClassFromName(targetClass)
+ for t_inst in route(inst, targetClass):
+ t_inst._SO_resetCache(targetAttr)
+
+
+
+ def FK(self, name):
+ '''route for foreign key'''
+ def f(inst, cls):
+ return cls.select(cls.q.id==getattr(inst, name+'ID'))
+ return f
+
+ def J(self, name):
+ '''route for join'''
+ def f(inst, cls):
+ return cls.select(getattr(cls.q,name)==inst.id)
+ return f
+
+ def S(self, *args):
+ '''route for self'''
+ if not len(args):
+ return self.S
+ return args[0]
+
+dep = SQLDependencyManager()
Added: SQLObject/branches/cache-deps-on-views/sqlobject/materialized/materialized.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/materialized/materialized.py (rev 0)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/materialized/materialized.py 2007-04-14 21:51:58 UTC (rev 2529)
@@ -0,0 +1,50 @@
+from sqlobject import SQLObject, sqlmeta as _sqlmeta
+from sqlobject import events
+from dependency import *
+
+class MaterializedSQLObject(SQLObject):
+
+ class sqlmeta(_sqlmeta):
+ pass
+
+def _addColumn(cls, connection, column_name, column_definition, changeSchema, post_funcs):
+ def f(cls, col):
+ if col.foreignKey:
+ dep.add((cls.__name__, col.name), (col.foreignKey, 'id'))
+ post_funcs.append(f)
+
+events.listen(_addColumn, MaterializedSQLObject, events.AddColumnSignal)
+
+def _addJoin(cls, join_name, join_definition, post_funcs):
+ def f(cls, join):
+ dep.add((cls.__name__, join.joinMethodName), (join.otherClassName, 'id'))
+ post_funcs.append(f)
+
+events.listen(_addJoin, MaterializedSQLObject, events.AddJoinSignal)
+
+
+class MaterializedAttr(object):
+ def __init__(self, func, colType, name=None):
+ self.func = func
+ self.colType = colType
+ if name is None:
+ self.name = self.func.__name__
+ else:
+ self.name = name
+
+ self.__name__ = self.name
+ self.__dict__.update(func.__dict__)
+
+ def __call__(self, obj):
+ val = None
+ if getattr(obj, '_SO_cacheInstance', None):
+ val = getattr(obj._SO_cacheInstance, self.name)
+ if val is None:
+ val = self.func(obj)
+ return val
+
+def cachedAs(colType, name=None):
+ def decorate(func):
+ return MaterializedAttr(func, colType, name=name)
+ return decorate
+
\ No newline at end of file
Property changes on: SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests
___________________________________________________________________
Name: svn:ignore
+ *.pyc
Added: SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/__init__.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/__init__.py (rev 0)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/__init__.py 2007-04-14 21:51:58 UTC (rev 2529)
@@ -0,0 +1 @@
+#
Added: SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_dependency.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_dependency.py (rev 0)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_dependency.py 2007-04-14 21:51:58 UTC (rev 2529)
@@ -0,0 +1,38 @@
+from py.test import raises
+from sqlobject import *
+from sqlobject.tests.dbtest import *
+from sqlobject.materialized import *
+
+
+class DependencyOne(SQLObject):
+ name = StringCol()
+ twos = SQLMultipleJoin('DependencyTwo', joinColumn='one_id')
+
+ @dep.dependentOn(('DependencyOne', 'twos'))
+ def _get_twoCount(self):
+ return self.twos.count()
+
+ @dep.dependentOn(('DependencyTwo', 'length'))
+ def _get_detailSize(self):
+ return self.twos.sum('length')
+
+class DependencyTwo(SQLObject):
+ detail = StringCol()
+ length = IntCol()
+ one = ForeignKey('DependencyOne')
+
+ @dep.dependentOn(('DependencyOne', 'name'))
+ def _get_name(self):
+ return self.one.name
+
+ @dep.dependentOn(('DependencyTwo', 'detail'))
+ @dep.dependentOn(('DependencyOne', 'name'))
+ def _get_name2(self):
+ return self.one.name + self.detail
+
+def setup_module(mod):
+ setupClass([DependencyOne, DependencyTwo])
+
+def testSetup():
+ assert dep.get(('DependencyOne', 'name')) == set([('DependencyTwo','_get_name'),
+ ('DependencyTwo','_get_name2')])
\ No newline at end of file
Added: SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_dependency_manager.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_dependency_manager.py (rev 0)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_dependency_manager.py 2007-04-14 21:51:58 UTC (rev 2529)
@@ -0,0 +1,129 @@
+from py.test import raises
+from sqlobject import *
+from sqlobject import declarative
+from sqlobject.tests.dbtest import *
+from sqlobject.materialized import *
+
+from pprint import pprint
+
+class A(object):
+ name = 'A'
+
+class B(object):
+ c = True
+ a = A()
+
+ def name(self):
+ return self.a.name
+
+ def condName(self):
+ if self.c:
+ return self.a.name
+ else:
+ return 'B'
+
+def test_addDependency():
+ dep = DependencyManager()
+ dep.add(('B', 'name'), ('A', 'name'))
+ assert dep.get(('A', 'name')) == set([('B', 'name')])
+
+def test_addDependencies():
+ dep = DependencyManager()
+ dep.add(('B', 'name'), ('A', 'name'))
+ dep.add(('B', 'condName'), ('A', 'name'))
+ assert dep.get(('A', 'name')) == set((('B', 'name'),
+ ('B', 'condName')))
+
+def test_addDepWithDecorator():
+ dep = DependencyManager()
+
+ class C(declarative.Declarative):
+
+ a = A()
+
+ @dep.dependentOn(('A', 'name'))
+ def name(self):
+ return self.a.name
+
+ assert dep.get(('A', 'name')) == set([('C', 'name')])
+
+def test_addDepsWithDecorator():
+ dep = DependencyManager()
+
+ class C(declarative.Declarative):
+
+ a = A()
+ b = B()
+
+ @dep.dependentOn(('B', 'name'))
+ @dep.dependentOn(('A', 'name'))
+ def name(self):
+ return self.a.name + self.b.name
+
+ assert dep.get(('A', 'name')) == set([('C', 'name')])
+ assert dep.get(('B', 'name')) == set([('C', 'name')])
+
+
+
+def test_depTreeFrom1():
+ dep = DependencyManager()
+ dep.add(('B', 'name'), ('A', 'name'))
+ assert dep.treeFrom(('A', 'name')) == {
+ ('B','name'): (('B','name'), {})
+ }
+
+def test_depTreeFrom2():
+ dep = DependencyManager()
+ dep.add(('B', 'name'), ('A', 'name'))
+ dep.add(('D', 'name'), ('A', 'name'))
+ dep.add(('C', 'cname'), ('B', 'name'))
+ pprint(dep.treeFrom(('A', 'name')))
+ assert dep.treeFrom(('A', 'name')) == {
+ ('B','name'): (('B', 'name'),
+ {
+ ('C', 'cname'): (('C', 'cname'),{})
+ }),
+ ('D','name'): (('D','name'), {})
+ }
+
+def test_depTreeTo1():
+ dep = DependencyManager()
+ dep.add(('B', 'name'), ('A', 'name'))
+ dep.add(('D', 'name'), ('A', 'name'))
+ dep.add(('C', 'cname'), ('B', 'name'))
+ pprint(dep.treeTo(('C', 'cname')))
+ assert dep.treeTo(('C', 'cname')) == {
+ ('B','name'): (('B','name'),
+ {
+ ('A', 'name'): (('A', 'name'),{})
+ }),
+ }
+
+def test_customItems():
+ class DItem(DependencyItem):
+ def __init__(self, (a,b,c)):
+ self.key = (a,b)
+ self.value = (b,c)
+
+ class Dep(Dependency):
+ DependencyItemClass = DItem
+
+ dep = DependencyManager()
+ dep.DependencyClass = Dep
+
+ dep.add(('b', 'x', 1), ('a', 'w', 1))
+ dep.add(('c', 'x', 2), ('a', 'w', 1))
+ dep.add(('d', 'y', 2), ('b', 'x', 1))
+ assert dep.treeFrom(('a','w')) == {
+ ('b','x'): (('x', 1),
+ {
+ ('d', 'y'): (('y', 2), {})
+ }),
+ ('c','x'): (('x', 2), {})
+ }
+
+ assert dep.get(('a','w')) == set([('x', 1), ('x', 2)])
+
+
+#def test_addDepWithSignal():
+# pass
Added: SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_materialized.py
===================================================================
--- SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_materialized.py (rev 0)
+++ SQLObject/branches/cache-deps-on-views/sqlobject/materialized/tests/test_materialized.py 2007-04-14 21:51:58 UTC (rev 2529)
@@ -0,0 +1,49 @@
+from py.test import raises
+from sqlobject import *
+from sqlobject.tests.dbtest import *
+from sqlobject.materialized import *
+
+class MaterializedOne(MaterializedSQLObject):
+ name = StringCol()
+ twos = SQLMultipleJoin('MaterializedTwo', joinColumn='one_id')
+
+ @cachedAs(IntCol)
+ @dep.dependentOn(('MaterializedOne', 'twos'))
+ def _get_twoCount(self):
+ return self.twos.count()
+
+ @cachedAs(IntCol)
+ @dep.dependentOn(('MaterializedTwo', 'length'))
+ def _get_detailSize(self):
+ return self.twos.sum('length')
+
+class MaterializedTwo(MaterializedSQLObject):
+ detail = StringCol()
+ length = IntCol()
+ one = ForeignKey('MaterializedOne')
+
+ @cachedAs(StringCol)
+ @dep.dependentOn(('MaterializedOne', 'name'))
+ def _get_name(self):
+ return self.one.name
+
+ @cachedAs(StringCol)
+ @dep.dependentOn(('MaterializedTwo', 'detail'))
+ @dep.dependentOn(('MaterializedOne', 'name'))
+ def _get_name2(self):
+ return self.one.name + self.detail
+
+def setup_module(mod):
+ setupClass([MaterializedOne, MaterializedTwo])
+
+def testSetup():
+ assert dep.get(('MaterializedOne', 'name')) == set([('MaterializedTwo','_get_name'),
+ ('MaterializedTwo','_get_name2')])
+
+def testSetupDepsForFK():
+ assert dep.get(('MaterializedOne', 'id')) == set([('MaterializedTwo', 'oneID')])
+
+def testSetupDepsForJoin():
+ assert dep.get(('MaterializedTwo', 'id')) == set([('MaterializedOne', 'twos')])
+
+
\ No newline at end of file
|