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 tableRegistryCopy.has_key(currentClass):
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 def addColumn(sqlmeta, columnDef, changeSchema=False, connection=None, childUpdate=False):
0097 soClass = sqlmeta.soClass
0098
0099
0100
0101 if sqlmeta.parentClass:
0102 for col in sqlmeta.parentClass.sqlmeta.columnList:
0103 cname = col.name
0104 if cname == 'childName': continue
0105 if cname.endswith("ID"): cname = cname[:-2]
0106 setattr(soClass, getterName(cname), eval(
0107 'lambda self: self._parent.%s' % cname))
0108 if not col.immutable:
0109 def make_setfunc(cname):
0110 def setfunc(self, val):
0111 if not self.sqlmeta._creating and not getattr(self.sqlmeta, "row_update_sig_suppress", False):
0112 self.sqlmeta.send(events.RowUpdateSignal, self, {cname : val})
0113
0114 result = setattr(self._parent, cname, val)
0115 return setfunc
0116
0117 setfunc = make_setfunc(cname)
0118 setattr(soClass, setterName(cname), setfunc)
0119 if childUpdate:
0120 makeProperties(soClass)
0121 return
0122
0123 if columnDef:
0124 super(InheritableSQLMeta, sqlmeta).addColumn(columnDef, changeSchema, connection)
0125
0126
0127
0128 if columnDef and hasattr(soClass, "q"):
0129 q = getattr(soClass.q, columnDef.name, None)
0130 else:
0131 q = None
0132 for c in sqlmeta.childClasses.values():
0133 c.sqlmeta.addColumn(columnDef, connection=connection, childUpdate=True)
0134 if q: setattr(c.q, columnDef.name, q)
0135
0136 addColumn = classmethod(addColumn)
0137
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 delColumn = classmethod(delColumn)
0161
0162 def addJoin(sqlmeta, joinDef, childUpdate=False):
0163 soClass = sqlmeta.soClass
0164
0165
0166
0167 if sqlmeta.parentClass:
0168 for join in sqlmeta.parentClass.sqlmeta.joins:
0169 jname = join.joinMethodName
0170 jarn = join.addRemoveName
0171 setattr(soClass, getterName(jname),
0172 eval('lambda self: self._parent.%s' % jname))
0173 if hasattr(join, 'remove'):
0174 setattr(soClass, 'remove' + jarn,
0175 eval('lambda self,o: self._parent.remove%s(o)' % jarn))
0176 if hasattr(join, 'add'):
0177 setattr(soClass, 'add' + jarn,
0178 eval('lambda self,o: self._parent.add%s(o)' % jarn))
0179 if childUpdate:
0180 makeProperties(soClass)
0181 return
0182
0183 if joinDef:
0184 super(InheritableSQLMeta, sqlmeta).addJoin(joinDef)
0185
0186
0187
0188 for c in sqlmeta.childClasses.values():
0189 c.sqlmeta.addJoin(joinDef, childUpdate=True)
0190
0191 addJoin = classmethod(addJoin)
0192
0193 def delJoin(sqlmeta, joinDef, childUpdate=False):
0194 if childUpdate:
0195 soClass = sqlmeta.soClass
0196 unmakeProperties(soClass)
0197 makeProperties(soClass)
0198 return
0199
0200 super(InheritableSQLMeta, sqlmeta).delJoin(joinDef)
0201
0202
0203
0204 for c in sqlmeta.childClasses.values():
0205 c.sqlmeta.delJoin(joinDef, childUpdate=True)
0206
0207 delJoin = classmethod(delJoin)
0208
0209 def getAllColumns(sqlmeta):
0210 columns = sqlmeta.columns.copy()
0211 sm = sqlmeta
0212 while sm.parentClass:
0213 columns.update(sm.parentClass.sqlmeta.columns)
0214 sm = sm.parentClass.sqlmeta
0215 return columns
0216 getAllColumns = classmethod(getAllColumns)
0217
0218 def getColumns(sqlmeta):
0219 columns = sqlmeta.getAllColumns()
0220 if columns.has_key('childName'):
0221 del columns['childName']
0222 return columns
0223 getColumns = classmethod(getColumns)
0224
0225
0226class InheritableSQLObject(SQLObject):
0227
0228 sqlmeta = InheritableSQLMeta
0229 _inheritable = True
0230 SelectResultsClass = InheritableSelectResults
0231
0232 def set(self, **kw):
0233 if self._parent:
0234 SQLObject.set(self, _suppress_set_sig=True, **kw)
0235 else:
0236 SQLObject.set(self, **kw)
0237
0238 def __classinit__(cls, new_attrs):
0239 SQLObject.__classinit__(cls, new_attrs)
0240
0241 currentClass = cls.sqlmeta.parentClass
0242 while currentClass:
0243 for column in currentClass.sqlmeta.columnDefinitions.values():
0244 if column.name == 'childName':
0245 continue
0246 if isinstance(column, ForeignKey):
0247 continue
0248 setattr(cls.q, column.name,
0249 getattr(currentClass.q, column.name))
0250 currentClass = currentClass.sqlmeta.parentClass
0251
0252
0253 def _SO_setupSqlmeta(cls, new_attrs, is_base):
0254
0255
0256
0257
0258 if cls.__name__ == "InheritableSQLObject":
0259 call_super = super(cls, cls)
0260 else:
0261
0262 call_super = super(InheritableSQLObject, cls)
0263 call_super._SO_setupSqlmeta(new_attrs, is_base)
0264 sqlmeta = cls.sqlmeta
0265 sqlmeta.childClasses = {}
0266
0267 sqlmeta.parentClass = None
0268 for superclass in cls.__bases__:
0269 if getattr(superclass, '_inheritable', False) and (superclass.__name__ != 'InheritableSQLObject'):
0271 if sqlmeta.parentClass:
0272
0273
0274 raise NotImplementedError(
0275 "Multiple inheritance is not implemented")
0276 sqlmeta.parentClass = superclass
0277 superclass.sqlmeta.childClasses[cls.__name__] = cls
0278 if sqlmeta.parentClass:
0279
0280 cls.sqlmeta.columns = {}
0281 cls.sqlmeta.columnList = []
0282 cls.sqlmeta.columnDefinitions = {}
0283
0284 if not sqlmeta.childName:
0285 sqlmeta.childName = cls.__name__
0286
0287 _SO_setupSqlmeta = classmethod(_SO_setupSqlmeta)
0288
0289 def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False):
0290
0291 val = super(InheritableSQLObject, cls).get(id, connection, selectResults)
0292
0293
0294 if childUpdate: return val
0295
0296 if 'childName' in cls.sqlmeta.columns:
0297 childName = val.childName
0298 if childName is not None:
0299 childClass = cls.sqlmeta.childClasses[childName]
0300
0301
0302
0303
0304
0305
0306
0307 if not (childResults or childClass.sqlmeta.columns):
0308 childResults = (None,)
0309 return childClass.get(id, connection=connection,
0310 selectResults=childResults)
0311
0312
0313 inst = val
0314 while inst.sqlmeta.parentClass and not inst._parent:
0315 inst._parent = inst.sqlmeta.parentClass.get(id,
0316 connection=connection, childUpdate=True)
0317 inst = inst._parent
0318
0319 return val
0320
0321 get = classmethod(get)
0322
0323 def _notifyFinishClassCreation(cls):
0324 sqlmeta = cls.sqlmeta
0325
0326 if sqlmeta.parentClass:
0327
0328 parentCols = sqlmeta.parentClass.sqlmeta.columns.keys()
0329 for column in sqlmeta.columnList:
0330 if column.name == 'childName':
0331 raise AttributeError(
0332 "The column name 'childName' is reserved")
0333 if column.name in parentCols:
0334 raise AttributeError("The column '%s' is"
0335 " already defined in an inheritable parent"
0336 % column.name)
0337
0338 if cls._inheritable and (cls.__name__ != 'InheritableSQLObject'):
0339 sqlmeta.addColumn(StringCol(name='childName',
0340
0341 length=255, default=None))
0342 if not sqlmeta.columnList:
0343
0344
0345 sqlmeta.addColumn(None)
0346 if not sqlmeta.joins:
0347
0348
0349 sqlmeta.addJoin(None)
0350 _notifyFinishClassCreation = classmethod(_notifyFinishClassCreation)
0351
0352 def _create(self, id, **kw):
0353
0354
0355
0356
0357
0358
0359 if kw.has_key('kw'):
0360 kw = kw['kw']
0361
0362
0363 if self.sqlmeta.parentClass:
0364 parentClass = self.sqlmeta.parentClass
0365 new_kw = {}
0366 parent_kw = {}
0367 for (name, value) in kw.items():
0368 if (name != 'childName') and hasattr(parentClass, name):
0369 parent_kw[name] = value
0370 else:
0371 new_kw[name] = value
0372 kw = new_kw
0373
0374
0375
0376
0377 for col in self.sqlmeta.columnList:
0378 if (col._default == sqlbuilder.NoDefault) and (col.name not in kw) and (col.foreignName not in kw):
0380 raise TypeError, "%s() did not get expected keyword argument %s" % (self.__class__.__name__, col.name)
0381
0382 parent_kw['childName'] = self.sqlmeta.childName
0383 self._parent = parentClass(kw=parent_kw,
0384 connection=self._connection)
0385
0386 id = self._parent.id
0387
0388
0389
0390 try:
0391 super(InheritableSQLObject, self)._create(id, **kw)
0392 except:
0393
0394 connection = self._connection
0395 if (not isinstance(connection, dbconnection.Transaction) and
0396 connection.autoCommit) and self.sqlmeta.parentClass:
0397 self._parent.destroySelf()
0398
0399 self._parent = None
0400
0401 raise
0402
0403 def _findAlternateID(cls, name, dbName, value, connection=None):
0404 result = list(cls.selectBy(connection, **{name: value}))
0405 if not result:
0406 return result, None
0407 obj = result[0]
0408 return [obj.id], obj
0409 _findAlternateID = classmethod(_findAlternateID)
0410
0411 def select(cls, clause=None, *args, **kwargs):
0412 parentClass = cls.sqlmeta.parentClass
0413 childUpdate = kwargs.pop('childUpdate', None)
0414
0415
0416
0417
0418
0419
0420
0421
0422
0423
0424
0425
0426
0427
0428
0429 if (not childUpdate) and parentClass:
0430 if childUpdate is None:
0431
0432 addClause = parentClass.q.childName == cls.sqlmeta.childName
0433
0434 if (clause is None) or (clause is sqlbuilder.SQLTrueClause) or (isinstance(clause, basestring) and (clause == 'all')):
0436 clause = addClause
0437 else:
0438
0439
0440
0441
0442 clsID = cls.q.id
0443 parentID = parentClass.q.id
0444 def _get_patched(clause):
0445 if isinstance(clause, sqlbuilder.SQLOp):
0446 _patch_id_clause(clause)
0447 return None
0448 elif not isinstance(clause, sqlbuilder.Field):
0449 return None
0450 elif (clause.tableName == clsID.tableName) and (clause.fieldName == clsID.fieldName):
0452 return parentID
0453 else:
0454 return None
0455 def _patch_id_clause(clause):
0456 if not isinstance(clause, sqlbuilder.SQLOp):
0457 return
0458 expr = _get_patched(clause.expr1)
0459 if expr:
0460 clause.expr1 = expr
0461 expr = _get_patched(clause.expr2)
0462 if expr:
0463 clause.expr2 = expr
0464 _patch_id_clause(clause)
0465
0466 clause = sqlbuilder.AND(clause, addClause)
0467 return parentClass.select(clause, childUpdate=False,
0468 *args, **kwargs)
0469 else:
0470 return super(InheritableSQLObject, cls).select(
0471 clause, *args, **kwargs)
0472 select = classmethod(select)
0473
0474 def selectBy(cls, connection=None, **kw):
0475 clause = []
0476 foreignColumns = {}
0477 currentClass = cls
0478 while currentClass:
0479 foreignColumns.update(dict([(column.foreignName, name)
0480 for (name, column) in currentClass.sqlmeta.columns.items()
0481 if column.foreignKey
0482 ]))
0483 currentClass = currentClass.sqlmeta.parentClass
0484 for name, value in kw.items():
0485 if name in foreignColumns:
0486 name = foreignColumns[name]
0487 if isinstance(value, SQLObject):
0488 value = value.id
0489 currentClass = cls
0490 while currentClass:
0491 try:
0492 clause.append(getattr(currentClass.q, name) == value)
0493 break
0494 except AttributeError, err:
0495 pass
0496 currentClass = currentClass.sqlmeta.parentClass
0497 else:
0498 raise AttributeError("'%s' instance has no attribute '%s'"
0499 % (cls.__name__, name))
0500 if clause:
0501 clause = reduce(sqlbuilder.AND, clause)
0502 else:
0503 clause = None
0504 conn = connection or cls._connection
0505 return cls.SelectResultsClass(cls, clause, connection=conn)
0506
0507 selectBy = classmethod(selectBy)
0508
0509 def destroySelf(self):
0510
0511 if hasattr(self, '_parent') and self._parent:
0512 self._parent.destroySelf()
0513 super(InheritableSQLObject, self).destroySelf()
0514
0515 def _reprItems(self):
0516 items = super(InheritableSQLObject, self)._reprItems()
0517
0518 if self.sqlmeta.parentClass:
0519 items.extend(self._parent._reprItems())
0520
0521 return [item for item in items if item[0] != 'childName']
0522
0523__all__ = ['InheritableSQLObject']