0001from sqlobject import dbconnection
0002from sqlobject import classregistry
0003from sqlobject import events
0004from sqlobject import sqlbuilder
0005from sqlobject.col import StringCol, ForeignKey
0006from sqlobject.main import sqlmeta, SQLObject, SelectResults, makeProperties, unmakeProperties, getterName, setterName
0008import iteration
0009
0010def tablesUsedSet(obj, db):
0011 if hasattr(obj, "tablesUsedSet"):
0012 return obj.tablesUsedSet(db)
0013 elif isinstance(obj, (tuple, list, set, frozenset)):
0014 s = set()
0015 for component in obj:
0016 s.update(tablesUsedSet(component, db))
0017 return s
0018 else:
0019 return set()
0020
0021
0022class InheritableSelectResults(SelectResults):
0023 IterationClass = iteration.InheritableIteration
0024
0025 def __init__(self, sourceClass, clause, clauseTables=None,
0026 inheritedTables=None, **ops):
0027 if clause is None or isinstance(clause, str) and clause == 'all':
0028 clause = sqlbuilder.SQLTrueClause
0029
0030 dbName = (ops.get('connection',None) or sourceClass._connection).dbName
0031
0032 tablesSet = tablesUsedSet(clause, dbName)
0033 tablesSet.add(str(sourceClass.sqlmeta.table))
0034 orderBy = ops.get('orderBy')
0035 if inheritedTables:
0036 for tableName in inheritedTables:
0037 tablesSet.add(str(tableName))
0038 if orderBy and not isinstance(orderBy, basestring):
0039 tablesSet.update(tablesUsedSet(orderBy, dbName))
0040
0041
0042
0043
0044
0045
0046 if not isinstance(clause, str):
0047 tableRegistry = {}
0048 allClasses = classregistry.registry(
0049 sourceClass.sqlmeta.registry).allClasses()
0050 for registryClass in allClasses:
0051 if str(registryClass.sqlmeta.table) in tablesSet:
0052
0053 tableRegistry[registryClass] = registryClass
0054 tableRegistryCopy = tableRegistry.copy()
0055 for childClass in tableRegistryCopy:
0056 if childClass not in tableRegistry:
0057 continue
0058 currentClass = childClass
0059 while currentClass:
0060 if currentClass in tableRegistryCopy:
0061 if currentClass in tableRegistry:
0062
0063
0064 del tableRegistry[currentClass]
0065
0066
0067 tableRegistry[childClass] = currentClass
0068 currentClass = currentClass.sqlmeta.parentClass
0069
0070
0071 parentClause = []
0072 for (currentClass, minParentClass) in tableRegistry.items():
0073 while (currentClass != minParentClass) and currentClass.sqlmeta.parentClass:
0075 parentClass = currentClass.sqlmeta.parentClass
0076 parentClause.append(currentClass.q.id == parentClass.q.id)
0077 currentClass = parentClass
0078 tablesSet.add(str(currentClass.sqlmeta.table))
0079 clause = reduce(sqlbuilder.AND, parentClause, clause)
0080
0081 super(InheritableSelectResults, self).__init__(sourceClass,
0082 clause, clauseTables, **ops)
0083
0084 def accumulateMany(self, *attributes, **kw):
0085 if kw.get("skipInherited"):
0086 return super(InheritableSelectResults, self).accumulateMany(*attributes)
0087 tables = []
0088 for func_name, attribute in attributes:
0089 if not isinstance(attribute, basestring):
0090 tables.append(attribute.tableName)
0091 clone = self.__class__(self.sourceClass, self.clause,
0092 self.clauseTables, inheritedTables=tables, **self.ops)
0093 return clone.accumulateMany(skipInherited=True, *attributes)
0094
0095class InheritableSQLMeta(sqlmeta):
0096 @classmethod
0097 def addColumn(sqlmeta, columnDef, changeSchema=False, connection=None, childUpdate=False):
0098 soClass = sqlmeta.soClass
0099
0100
0101
0102 if sqlmeta.parentClass:
0103 for col in sqlmeta.parentClass.sqlmeta.columnList:
0104 cname = col.name
0105 if cname == 'childName': continue
0106 if cname.endswith("ID"): cname = cname[:-2]
0107 setattr(soClass, getterName(cname), eval(
0108 'lambda self: self._parent.%s' % cname))
0109 if not col.immutable:
0110 def make_setfunc(cname):
0111 def setfunc(self, val):
0112 if not self.sqlmeta._creating and not getattr(self.sqlmeta, "row_update_sig_suppress", False):
0113 self.sqlmeta.send(events.RowUpdateSignal, self, {cname : val})
0114
0115 result = setattr(self._parent, cname, val)
0116 return setfunc
0117
0118 setfunc = make_setfunc(cname)
0119 setattr(soClass, setterName(cname), setfunc)
0120 if childUpdate:
0121 makeProperties(soClass)
0122 return
0123
0124 if columnDef:
0125 super(InheritableSQLMeta, sqlmeta).addColumn(columnDef, changeSchema, connection)
0126
0127
0128
0129 if columnDef and hasattr(soClass, "q"):
0130 q = getattr(soClass.q, columnDef.name, None)
0131 else:
0132 q = None
0133 for c in sqlmeta.childClasses.values():
0134 c.sqlmeta.addColumn(columnDef, connection=connection, childUpdate=True)
0135 if q: setattr(c.q, columnDef.name, q)
0136
0137 @classmethod
0138 def delColumn(sqlmeta, column, changeSchema=False, connection=None, childUpdate=False):
0139 if childUpdate:
0140 soClass = sqlmeta.soClass
0141 unmakeProperties(soClass)
0142 makeProperties(soClass)
0143
0144 if isinstance(column, str):
0145 name = column
0146 else:
0147 name = column.name
0148 delattr(soClass, name)
0149 delattr(soClass.q, name)
0150 return
0151
0152 super(InheritableSQLMeta, sqlmeta).delColumn(column, changeSchema, connection)
0153
0154
0155
0156 for c in sqlmeta.childClasses.values():
0157 c.sqlmeta.delColumn(column, changeSchema=changeSchema,
0158 connection=connection, childUpdate=True)
0159
0160 @classmethod
0161 def addJoin(sqlmeta, joinDef, childUpdate=False):
0162 soClass = sqlmeta.soClass
0163
0164
0165
0166 if sqlmeta.parentClass:
0167 for join in sqlmeta.parentClass.sqlmeta.joins:
0168 jname = join.joinMethodName
0169 jarn = join.addRemoveName
0170 setattr(soClass, getterName(jname),
0171 eval('lambda self: self._parent.%s' % jname))
0172 if hasattr(join, 'remove'):
0173 setattr(soClass, 'remove' + jarn,
0174 eval('lambda self,o: self._parent.remove%s(o)' % jarn))
0175 if hasattr(join, 'add'):
0176 setattr(soClass, 'add' + jarn,
0177 eval('lambda self,o: self._parent.add%s(o)' % jarn))
0178 if childUpdate:
0179 makeProperties(soClass)
0180 return
0181
0182 if joinDef:
0183 super(InheritableSQLMeta, sqlmeta).addJoin(joinDef)
0184
0185
0186
0187 for c in sqlmeta.childClasses.values():
0188 c.sqlmeta.addJoin(joinDef, childUpdate=True)
0189
0190 @classmethod
0191 def delJoin(sqlmeta, joinDef, childUpdate=False):
0192 if childUpdate:
0193 soClass = sqlmeta.soClass
0194 unmakeProperties(soClass)
0195 makeProperties(soClass)
0196 return
0197
0198 super(InheritableSQLMeta, sqlmeta).delJoin(joinDef)
0199
0200
0201
0202 for c in sqlmeta.childClasses.values():
0203 c.sqlmeta.delJoin(joinDef, childUpdate=True)
0204
0205 @classmethod
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 @classmethod
0215 def getColumns(sqlmeta):
0216 columns = sqlmeta.getAllColumns()
0217 if 'childName' in columns:
0218 del columns['childName']
0219 return columns
0220
0221
0222class InheritableSQLObject(SQLObject):
0223
0224 sqlmeta = InheritableSQLMeta
0225 _inheritable = True
0226 SelectResultsClass = InheritableSelectResults
0227
0228 def set(self, **kw):
0229 if self._parent:
0230 SQLObject.set(self, _suppress_set_sig=True, **kw)
0231 else:
0232 SQLObject.set(self, **kw)
0233
0234 def __classinit__(cls, new_attrs):
0235 SQLObject.__classinit__(cls, new_attrs)
0236
0237 currentClass = cls.sqlmeta.parentClass
0238 while currentClass:
0239 for column in currentClass.sqlmeta.columnDefinitions.values():
0240 if column.name == 'childName':
0241 continue
0242 if isinstance(column, ForeignKey):
0243 continue
0244 setattr(cls.q, column.name,
0245 getattr(currentClass.q, column.name))
0246 currentClass = currentClass.sqlmeta.parentClass
0247
0248 @classmethod
0249 def _SO_setupSqlmeta(cls, new_attrs, is_base):
0250
0251
0252
0253
0254 if cls.__name__ == "InheritableSQLObject":
0255 call_super = super(cls, cls)
0256 else:
0257
0258 call_super = super(InheritableSQLObject, cls)
0259 call_super._SO_setupSqlmeta(new_attrs, is_base)
0260 sqlmeta = cls.sqlmeta
0261 sqlmeta.childClasses = {}
0262
0263 sqlmeta.parentClass = None
0264 for superclass in cls.__bases__:
0265 if getattr(superclass, '_inheritable', False) and (superclass.__name__ != 'InheritableSQLObject'):
0267 if sqlmeta.parentClass:
0268
0269
0270 raise NotImplementedError(
0271 "Multiple inheritance is not implemented")
0272 sqlmeta.parentClass = superclass
0273 superclass.sqlmeta.childClasses[cls.__name__] = cls
0274 if sqlmeta.parentClass:
0275
0276 cls.sqlmeta.columns = {}
0277 cls.sqlmeta.columnList = []
0278 cls.sqlmeta.columnDefinitions = {}
0279
0280 if not sqlmeta.childName:
0281 sqlmeta.childName = cls.__name__
0282
0283 @classmethod
0284 def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False):
0285
0286 val = super(InheritableSQLObject, cls).get(id, connection, selectResults)
0287
0288
0289 if childUpdate: return val
0290
0291 if 'childName' in cls.sqlmeta.columns:
0292 childName = val.childName
0293 if childName is not None:
0294 childClass = cls.sqlmeta.childClasses[childName]
0295
0296
0297
0298
0299
0300
0301
0302 if not (childResults or childClass.sqlmeta.columns):
0303 childResults = (None,)
0304 return childClass.get(id, connection=connection,
0305 selectResults=childResults)
0306
0307
0308 inst = val
0309 while inst.sqlmeta.parentClass and not inst._parent:
0310 inst._parent = inst.sqlmeta.parentClass.get(id,
0311 connection=connection, childUpdate=True)
0312 inst = inst._parent
0313
0314 return val
0315
0316 @classmethod
0317 def _notifyFinishClassCreation(cls):
0318 sqlmeta = cls.sqlmeta
0319
0320 if sqlmeta.parentClass:
0321
0322 parentCols = sqlmeta.parentClass.sqlmeta.columns.keys()
0323 for column in sqlmeta.columnList:
0324 if column.name == 'childName':
0325 raise AttributeError(
0326 "The column name 'childName' is reserved")
0327 if column.name in parentCols:
0328 raise AttributeError("The column '%s' is"
0329 " already defined in an inheritable parent"
0330 % column.name)
0331
0332 if cls._inheritable and (cls.__name__ != 'InheritableSQLObject'):
0333 sqlmeta.addColumn(StringCol(name='childName',
0334
0335 length=255, default=None))
0336 if not sqlmeta.columnList:
0337
0338
0339 sqlmeta.addColumn(None)
0340 if not sqlmeta.joins:
0341
0342
0343 sqlmeta.addJoin(None)
0344
0345 def _create(self, id, **kw):
0346
0347
0348
0349
0350
0351
0352 if 'kw' in kw:
0353 kw = kw['kw']
0354
0355
0356 if self.sqlmeta.parentClass:
0357 parentClass = self.sqlmeta.parentClass
0358 new_kw = {}
0359 parent_kw = {}
0360 for (name, value) in kw.items():
0361 if (name != 'childName') and hasattr(parentClass, name):
0362 parent_kw[name] = value
0363 else:
0364 new_kw[name] = value
0365 kw = new_kw
0366
0367
0368
0369
0370 for col in self.sqlmeta.columnList:
0371 if (col._default == sqlbuilder.NoDefault) and (col.name not in kw) and (col.foreignName not in kw):
0373 raise TypeError, "%s() did not get expected keyword argument %s" % (self.__class__.__name__, col.name)
0374
0375 parent_kw['childName'] = self.sqlmeta.childName
0376 self._parent = parentClass(kw=parent_kw,
0377 connection=self._connection)
0378
0379 id = self._parent.id
0380
0381
0382
0383 try:
0384 super(InheritableSQLObject, self)._create(id, **kw)
0385 except:
0386
0387 connection = self._connection
0388 if (not isinstance(connection, dbconnection.Transaction) and
0389 connection.autoCommit) and self.sqlmeta.parentClass:
0390 self._parent.destroySelf()
0391
0392 self._parent = None
0393
0394 raise
0395
0396 @classmethod
0397 def _findAlternateID(cls, name, dbName, value, connection=None):
0398 result = list(cls.selectBy(connection, **{name: value}))
0399 if not result:
0400 return result, None
0401 obj = result[0]
0402 return [obj.id], obj
0403
0404 @classmethod
0405 def select(cls, clause=None, *args, **kwargs):
0406 parentClass = cls.sqlmeta.parentClass
0407 childUpdate = kwargs.pop('childUpdate', None)
0408
0409
0410
0411
0412
0413
0414
0415
0416
0417
0418
0419
0420
0421
0422
0423 if (not childUpdate) and parentClass:
0424 if childUpdate is None:
0425
0426 addClause = parentClass.q.childName == cls.sqlmeta.childName
0427
0428 if (clause is None) or (clause is sqlbuilder.SQLTrueClause) or (isinstance(clause, basestring) and (clause == 'all')):
0430 clause = addClause
0431 else:
0432
0433
0434
0435
0436 clsID = cls.q.id
0437 parentID = parentClass.q.id
0438 def _get_patched(clause):
0439 if isinstance(clause, sqlbuilder.SQLOp):
0440 _patch_id_clause(clause)
0441 return None
0442 elif not isinstance(clause, sqlbuilder.Field):
0443 return None
0444 elif (clause.tableName == clsID.tableName) and (clause.fieldName == clsID.fieldName):
0446 return parentID
0447 else:
0448 return None
0449 def _patch_id_clause(clause):
0450 if not isinstance(clause, sqlbuilder.SQLOp):
0451 return
0452 expr = _get_patched(clause.expr1)
0453 if expr:
0454 clause.expr1 = expr
0455 expr = _get_patched(clause.expr2)
0456 if expr:
0457 clause.expr2 = expr
0458 _patch_id_clause(clause)
0459
0460 clause = sqlbuilder.AND(clause, addClause)
0461 return parentClass.select(clause, childUpdate=False,
0462 *args, **kwargs)
0463 else:
0464 return super(InheritableSQLObject, cls).select(
0465 clause, *args, **kwargs)
0466
0467 @classmethod
0468 def selectBy(cls, connection=None, **kw):
0469 clause = []
0470 foreignColumns = {}
0471 currentClass = cls
0472 while currentClass:
0473 foreignColumns.update(dict([(column.foreignName, name)
0474 for (name, column) in currentClass.sqlmeta.columns.items()
0475 if column.foreignKey
0476 ]))
0477 currentClass = currentClass.sqlmeta.parentClass
0478 for name, value in kw.items():
0479 if name in foreignColumns:
0480 name = foreignColumns[name]
0481 if isinstance(value, SQLObject):
0482 value = value.id
0483 currentClass = cls
0484 while currentClass:
0485 try:
0486 clause.append(getattr(currentClass.q, name) == value)
0487 break
0488 except AttributeError, err:
0489 pass
0490 currentClass = currentClass.sqlmeta.parentClass
0491 else:
0492 raise AttributeError("'%s' instance has no attribute '%s'"
0493 % (cls.__name__, name))
0494 if clause:
0495 clause = reduce(sqlbuilder.AND, clause)
0496 else:
0497 clause = None
0498 conn = connection or cls._connection
0499 return cls.SelectResultsClass(cls, clause, connection=conn)
0500
0501 def destroySelf(self):
0502
0503 if hasattr(self, '_parent') and self._parent:
0504 self._parent.destroySelf()
0505 super(InheritableSQLObject, self).destroySelf()
0506
0507 def _reprItems(self):
0508 items = super(InheritableSQLObject, self)._reprItems()
0509
0510 if self.sqlmeta.parentClass:
0511 items.extend(self._parent._reprItems())
0512
0513 return [item for item in items if item[0] != 'childName']
0514
0515__all__ = ['InheritableSQLObject']