0001from sqlobject import sqlbuilder
0002from sqlobject import classregistry
0003from sqlobject.col import StringCol, ForeignKey
0004from sqlobject.main import sqlmeta, SQLObject, SelectResults, makeProperties, unmakeProperties, getterName, setterName
0006import iteration
0007
0008try:
0009 set
0010except NameError:
0011 from sets import Set, ImmutableSet
0012 set, frozenset = Set, ImmutableSet
0013
0014def tablesUsedSet(obj, db):
0015 if hasattr(obj, "tablesUsedSet"):
0016 return obj.tablesUsedSet(db)
0017 elif isinstance(obj, (tuple, list, set, frozenset)):
0018 s = set()
0019 for component in obj:
0020 s.update(tablesUsedSet(component, db))
0021 return s
0022 else:
0023 return set()
0024
0025
0026class InheritableSelectResults(SelectResults):
0027 IterationClass = iteration.InheritableIteration
0028
0029 def __init__(self, sourceClass, clause, clauseTables=None,
0030 inheritedTables=None, **ops):
0031 if clause is None or isinstance(clause, str) and clause == 'all':
0032 clause = sqlbuilder.SQLTrueClause
0033
0034 dbName = (ops.get('connection',None) or sourceClass._connection).dbName
0035
0036 tablesSet = tablesUsedSet(clause, dbName)
0037 tablesSet.add(str(sourceClass.sqlmeta.table))
0038 orderBy = ops.get('orderBy')
0039 if inheritedTables:
0040 for tableName in inheritedTables:
0041 tablesSet.add(str(tableName))
0042 if orderBy and not isinstance(orderBy, basestring):
0043 tablesSet.update(tablesUsedSet(orderBy, dbName))
0044
0045
0046
0047
0048
0049
0050 if not isinstance(clause, str):
0051 tableRegistry = {}
0052 allClasses = classregistry.registry(
0053 sourceClass.sqlmeta.registry).allClasses()
0054 for registryClass in allClasses:
0055 if str(registryClass.sqlmeta.table) in tablesSet:
0056
0057 tableRegistry[registryClass] = registryClass
0058 tableRegistryCopy = tableRegistry.copy()
0059 for childClass in tableRegistryCopy:
0060 if childClass not in tableRegistry:
0061 continue
0062 currentClass = childClass
0063 while currentClass:
0064 if tableRegistryCopy.has_key(currentClass):
0065 if currentClass in tableRegistry:
0066
0067
0068 del tableRegistry[currentClass]
0069
0070
0071 tableRegistry[childClass] = currentClass
0072 currentClass = currentClass.sqlmeta.parentClass
0073
0074
0075 parentClause = []
0076 for (currentClass, minParentClass) in tableRegistry.items():
0077 while (currentClass != minParentClass) and currentClass.sqlmeta.parentClass:
0079 parentClass = currentClass.sqlmeta.parentClass
0080 parentClause.append(currentClass.q.id == parentClass.q.id)
0081 currentClass = parentClass
0082 tablesSet.add(str(currentClass.sqlmeta.table))
0083 clause = reduce(sqlbuilder.AND, parentClause, clause)
0084
0085 super(InheritableSelectResults, self).__init__(sourceClass,
0086 clause, clauseTables, **ops)
0087
0088 def accumulateMany(self, *attributes, **kw):
0089 if kw.get("skipInherited"):
0090 return super(InheritableSelectResults, self).accumulateMany(*attributes)
0091 tables = []
0092 for func_name, attribute in attributes:
0093 if not isinstance(attribute, basestring):
0094 tables.append(attribute.tableName)
0095 clone = self.__class__(self.sourceClass, self.clause,
0096 self.clauseTables, inheritedTables=tables, **self.ops)
0097 return clone.accumulateMany(skipInherited=True, *attributes)
0098
0099class InheritableSQLMeta(sqlmeta):
0100 def addColumn(sqlmeta, columnDef, changeSchema=False, connection=None, childUpdate=False):
0101 soClass = sqlmeta.soClass
0102
0103
0104
0105 if sqlmeta.parentClass:
0106 for col in sqlmeta.parentClass.sqlmeta.columnList:
0107 cname = col.name
0108 if cname == 'childName': continue
0109 if cname.endswith("ID"): cname = cname[:-2]
0110 setattr(soClass, getterName(cname), eval(
0111 'lambda self: self._parent.%s' % cname))
0112 if not col.immutable:
0113 setattr(soClass, setterName(cname), eval(
0114 'lambda self, val: setattr(self._parent, %s, val)'
0115 % repr(cname)))
0116 if childUpdate:
0117 makeProperties(soClass)
0118 return
0119
0120 if columnDef:
0121 super(InheritableSQLMeta, sqlmeta).addColumn(columnDef, changeSchema, connection)
0122
0123
0124
0125 if columnDef and hasattr(soClass, "q"):
0126 q = getattr(soClass.q, columnDef.name, None)
0127 else:
0128 q = None
0129 for c in sqlmeta.childClasses.values():
0130 c.sqlmeta.addColumn(columnDef, connection=connection, childUpdate=True)
0131 if q: setattr(c.q, columnDef.name, q)
0132
0133 addColumn = classmethod(addColumn)
0134
0135 def delColumn(sqlmeta, column, changeSchema=False, connection=None, childUpdate=False):
0136 if childUpdate:
0137 soClass = sqlmeta.soClass
0138 unmakeProperties(soClass)
0139 makeProperties(soClass)
0140
0141 if isinstance(column, str):
0142 name = column
0143 else:
0144 name = column.name
0145 delattr(soClass, name)
0146 delattr(soClass.q, name)
0147 return
0148
0149 super(InheritableSQLMeta, sqlmeta).delColumn(column, changeSchema, connection)
0150
0151
0152
0153 for c in sqlmeta.childClasses.values():
0154 c.sqlmeta.delColumn(column, changeSchema=changeSchema,
0155 connection=connection, childUpdate=True)
0156
0157 delColumn = classmethod(delColumn)
0158
0159 def addJoin(sqlmeta, joinDef, childUpdate=False):
0160 soClass = sqlmeta.soClass
0161
0162
0163
0164 if sqlmeta.parentClass:
0165 for join in sqlmeta.parentClass.sqlmeta.joins:
0166 jname = join.joinMethodName
0167 jarn = join.addRemoveName
0168 setattr(soClass, getterName(jname),
0169 eval('lambda self: self._parent.%s' % jname))
0170 if hasattr(join, 'remove'):
0171 setattr(soClass, 'remove' + jarn,
0172 eval('lambda self,o: self._parent.remove%s(o)' % jarn))
0173 if hasattr(join, 'add'):
0174 setattr(soClass, 'add' + jarn,
0175 eval('lambda self,o: self._parent.add%s(o)' % jarn))
0176 if childUpdate:
0177 makeProperties(soClass)
0178 return
0179
0180 if joinDef:
0181 super(InheritableSQLMeta, sqlmeta).addJoin(joinDef)
0182
0183
0184
0185 for c in sqlmeta.childClasses.values():
0186 c.sqlmeta.addJoin(joinDef, childUpdate=True)
0187
0188 addJoin = classmethod(addJoin)
0189
0190 def delJoin(sqlmeta, joinDef, childUpdate=False):
0191 if childUpdate:
0192 soClass = sqlmeta.soClass
0193 unmakeProperties(soClass)
0194 makeProperties(soClass)
0195 return
0196
0197 super(InheritableSQLMeta, sqlmeta).delJoin(joinDef)
0198
0199
0200
0201 for c in sqlmeta.childClasses.values():
0202 c.sqlmeta.delJoin(joinDef, childUpdate=True)
0203
0204 delJoin = classmethod(delJoin)
0205
0206 def getAllColumns(sqlmeta):
0207 columns = sqlmeta.columns.copy()
0208 sm = sqlmeta
0209 while sm.parentClass:
0210 columns.update(sm.parentClass.sqlmeta.columns)
0211 sm = sm.parentClass.sqlmeta
0212 return columns
0213
0214 def getColumns(sqlmeta):
0215 columns = sqlmeta.getAllColumns()
0216 if columns.has_key('childName'):
0217 del columns['childName']
0218 return columns
0219
0220
0221class InheritableSQLObject(SQLObject):
0222
0223 sqlmeta = InheritableSQLMeta
0224 _inheritable = True
0225 SelectResultsClass = InheritableSelectResults
0226
0227 def __classinit__(cls, new_attrs):
0228 SQLObject.__classinit__(cls, new_attrs)
0229
0230 currentClass = cls.sqlmeta.parentClass
0231 while currentClass:
0232 for column in currentClass.sqlmeta.columnDefinitions.values():
0233 if column.name == 'childName':
0234 continue
0235 if isinstance(column, ForeignKey):
0236 continue
0237 setattr(cls.q, column.name,
0238 getattr(currentClass.q, column.name))
0239 currentClass = currentClass.sqlmeta.parentClass
0240
0241
0242 def _SO_setupSqlmeta(cls, new_attrs, is_base):
0243
0244
0245
0246
0247 if cls.__name__ == "InheritableSQLObject":
0248 call_super = super(cls, cls)
0249 else:
0250
0251 call_super = super(InheritableSQLObject, cls)
0252 call_super._SO_setupSqlmeta(new_attrs, is_base)
0253 sqlmeta = cls.sqlmeta
0254 sqlmeta.childClasses = {}
0255
0256 sqlmeta.parentClass = None
0257 for superclass in cls.__bases__:
0258 if getattr(superclass, '_inheritable', False) and (superclass.__name__ != 'InheritableSQLObject'):
0260 if sqlmeta.parentClass:
0261
0262
0263 raise NotImplementedError(
0264 "Multiple inheritance is not implemented")
0265 sqlmeta.parentClass = superclass
0266 superclass.sqlmeta.childClasses[cls.__name__] = cls
0267 if sqlmeta.parentClass:
0268
0269 cls.sqlmeta.columns = {}
0270 cls.sqlmeta.columnList = []
0271 cls.sqlmeta.columnDefinitions = {}
0272
0273 if not sqlmeta.childName:
0274 sqlmeta.childName = cls.__name__
0275
0276 _SO_setupSqlmeta = classmethod(_SO_setupSqlmeta)
0277
0278 def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False):
0279
0280 val = super(InheritableSQLObject, cls).get(id, connection, selectResults)
0281
0282
0283 if childUpdate: return val
0284
0285 if 'childName' in cls.sqlmeta.columns:
0286 childName = val.childName
0287 if childName is not None:
0288 childClass = cls.sqlmeta.childClasses[childName]
0289
0290
0291
0292
0293
0294
0295
0296 if not (childResults or childClass.sqlmeta.columns):
0297 childResults = (None,)
0298 return childClass.get(id, connection=connection,
0299 selectResults=childResults)
0300
0301
0302 inst = val
0303 while inst.sqlmeta.parentClass and not inst._parent:
0304 inst._parent = inst.sqlmeta.parentClass.get(id,
0305 connection=connection, childUpdate=True)
0306 inst = inst._parent
0307
0308 return val
0309
0310 get = classmethod(get)
0311
0312 def _notifyFinishClassCreation(cls):
0313 sqlmeta = cls.sqlmeta
0314
0315 if sqlmeta.parentClass:
0316
0317 parentCols = sqlmeta.parentClass.sqlmeta.columns.keys()
0318 for column in sqlmeta.columnList:
0319 if column.name == 'childName':
0320 raise AttributeError(
0321 "The column name 'childName' is reserved")
0322 if column.name in parentCols:
0323 raise AttributeError("The column '%s' is"
0324 " already defined in an inheritable parent"
0325 % column.name)
0326
0327 if cls._inheritable and (cls.__name__ != 'InheritableSQLObject'):
0328 sqlmeta.addColumn(StringCol(name='childName',
0329
0330 length=255, default=None))
0331 if not sqlmeta.columnList:
0332
0333
0334 sqlmeta.addColumn(None)
0335 if not sqlmeta.joins:
0336
0337
0338 sqlmeta.addJoin(None)
0339 _notifyFinishClassCreation = classmethod(_notifyFinishClassCreation)
0340
0341 def _create(self, id, **kw):
0342
0343
0344
0345
0346
0347
0348 if kw.has_key('kw'):
0349 kw = kw['kw']
0350
0351
0352 if self.sqlmeta.parentClass:
0353 parentClass = self.sqlmeta.parentClass
0354 new_kw = {}
0355 parent_kw = {}
0356 for (name, value) in kw.items():
0357 if (name != 'childName') and hasattr(parentClass, name):
0358 parent_kw[name] = value
0359 else:
0360 new_kw[name] = value
0361 kw = new_kw
0362
0363
0364
0365
0366 for col in self.sqlmeta.columnList:
0367 if (col._default == sqlbuilder.NoDefault) and (col.name not in kw) and (col.foreignName not in kw):
0369 raise TypeError, "%s() did not get expected keyword argument %s" % (self.__class__.__name__, col.name)
0370
0371 parent_kw['childName'] = self.sqlmeta.childName
0372 self._parent = parentClass(kw=parent_kw,
0373 connection=self._connection)
0374
0375 id = self._parent.id
0376
0377 super(InheritableSQLObject, self)._create(id, **kw)
0378
0379 def _findAlternateID(cls, name, dbName, value, connection=None):
0380 result = list(cls.selectBy(connection, **{name: value}))
0381 if not result:
0382 return result, None
0383 obj = result[0]
0384 return [obj.id], obj
0385 _findAlternateID = classmethod(_findAlternateID)
0386
0387 def select(cls, clause=None, *args, **kwargs):
0388 parentClass = cls.sqlmeta.parentClass
0389 childUpdate = kwargs.pop('childUpdate', None)
0390
0391
0392
0393
0394
0395
0396
0397
0398
0399
0400
0401
0402
0403
0404
0405 if (not childUpdate) and parentClass:
0406 if childUpdate is None:
0407
0408