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