0001"""
0002SQLObject
0003---------
0004
0005:author: Ian Bicking <ianb@colorstudy.com>
0006
0007SQLObject is a object-relational mapper. See SQLObject.html or
0008SQLObject.txt for more.
0009
0010With the help by Oleg Broytman and many other contributors.
0011See Authors.txt.
0012
0013This program is free software; you can redistribute it and/or modify
0014it under the terms of the GNU Lesser General Public License as
0015published by the Free Software Foundation; either version 2.1 of the
0016License, or (at your option) any later version.
0017
0018This program is distributed in the hope that it will be useful,
0019but WITHOUT ANY WARRANTY; without even the implied warranty of
0020MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0021GNU General Public License for more details.
0022
0023You should have received a copy of the GNU Lesser General Public
0024License along with this program; if not, write to the Free Software
0025Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
0026USA.
0027"""
0028
0029import threading
0030import weakref
0031import sqlbuilder
0032import dbconnection
0033import col
0034import styles
0035import types
0036import warnings
0037import joins
0038import index
0039import classregistry
0040import declarative
0041import events
0042from sresults import SelectResults
0043from util.threadinglocal import local
0044
0045import sys
0046if sys.version_info[:3] < (2, 6, 0):
0047 raise ImportError, "SQLObject requires Python 2.6 or 2.7"
0048
0049"""
0050This thread-local storage is needed for RowCreatedSignals. It gathers
0051code-blocks to execute _after_ the whole hierachy of inherited SQLObjects
0052is created. See SQLObject._create
0053"""
0054
0055NoDefault = sqlbuilder.NoDefault
0056
0057class SQLObjectNotFound(LookupError): pass
0058class SQLObjectIntegrityError(Exception): pass
0059
0060def makeProperties(obj):
0061 """
0062 This function takes a dictionary of methods and finds
0063 methods named like:
0064 * _get_attr
0065 * _set_attr
0066 * _del_attr
0067 * _doc_attr
0068 Except for _doc_attr, these should be methods. It
0069 then creates properties from these methods, like
0070 property(_get_attr, _set_attr, _del_attr, _doc_attr).
0071 Missing methods are okay.
0072 """
0073
0074 if isinstance(obj, dict):
0075 def setFunc(var, value):
0076 obj[var] = value
0077 d = obj
0078 else:
0079 def setFunc(var, value):
0080 setattr(obj, var, value)
0081 d = obj.__dict__
0082
0083 props = {}
0084 for var, value in d.items():
0085 if var.startswith('_set_'):
0086 props.setdefault(var[5:], {})['set'] = value
0087 elif var.startswith('_get_'):
0088 props.setdefault(var[5:], {})['get'] = value
0089 elif var.startswith('_del_'):
0090 props.setdefault(var[5:], {})['del'] = value
0091 elif var.startswith('_doc_'):
0092 props.setdefault(var[5:], {})['doc'] = value
0093 for var, setters in props.items():
0094 if len(setters) == 1 and 'doc' in setters:
0095 continue
0096 if var in d:
0097 if isinstance(d[var], (types.MethodType, types.FunctionType)):
0098 warnings.warn(
0099 "I tried to set the property %r, but it was "
0100 "already set, as a method (%r). Methods have "
0101 "significantly different semantics than properties, "
0102 "and this may be a sign of a bug in your code."
0103 % (var, d[var]))
0104 continue
0105 setFunc(var,
0106 property(setters.get('get'), setters.get('set'),
0107 setters.get('del'), setters.get('doc')))
0108
0109def unmakeProperties(obj):
0110 if isinstance(obj, dict):
0111 def delFunc(obj, var):
0112 del obj[var]
0113 d = obj
0114 else:
0115 delFunc = delattr
0116 d = obj.__dict__
0117
0118 for var, value in d.items():
0119 if isinstance(value, property):
0120 for prop in [value.fget, value.fset, value.fdel]:
0121 if prop and not prop.__name__ in d:
0122 delFunc(obj, var)
0123 break
0124
0125def findDependencies(name, registry=None):
0126 depends = []
0127 for klass in classregistry.registry(registry).allClasses():
0128 if findDependantColumns(name, klass):
0129 depends.append(klass)
0130 else:
0131 for join in klass.sqlmeta.joins:
0132 if isinstance(join, joins.SORelatedJoin) and join.otherClassName == name:
0133 depends.append(klass)
0134 break
0135 return depends
0136
0137def findDependantColumns(name, klass):
0138 depends = []
0139 for col in klass.sqlmeta.columnList:
0140 if col.foreignKey == name and col.cascade is not None:
0141 depends.append(col)
0142 return depends
0143
0144def _collectAttributes(cls, new_attrs, look_for_class):
0145 """Finds all attributes in `new_attrs` that are instances of
0146 `look_for_class`. The ``.name`` attribute is set for any matching objects.
0147 Returns them as a list.
0148
0149 """
0150 result = []
0151 for attr, value in new_attrs.items():
0152 if isinstance(value, look_for_class):
0153 value.name = attr
0154 delattr(cls, attr)
0155 result.append(value)
0156 return result
0157
0158class CreateNewSQLObject:
0159 """
0160 Dummy singleton to use in place of an ID, to signal we want
0161 a new object.
0162 """
0163 pass
0164
0165class sqlmeta(object):
0166
0167 """
0168 This object is the object we use to keep track of all sorts of
0169 information. Subclasses are made for each SQLObject subclass
0170 (dynamically if necessary), and instances are created to go
0171 alongside every SQLObject instance.
0172 """
0173
0174 table = None
0175 idName = None
0176 idSequence = None
0177
0178
0179
0180 idType = int
0181 style = None
0182 lazyUpdate = False
0183 defaultOrder = None
0184 cacheValues = True
0185 registry = None
0186 fromDatabase = False
0187
0188
0189 expired = False
0190
0191
0192
0193 columns = {}
0194 columnList = []
0195
0196
0197
0198
0199 columnDefinitions = {}
0200
0201
0202 indexes = []
0203 indexDefinitions = []
0204 joins = []
0205 joinDefinitions = []
0206
0207
0208 _unshared_attributes = ['table', 'columns', 'childName']
0209
0210
0211
0212
0213
0214
0215
0216
0217
0218
0219
0220
0221 _creating = False
0222 _obsolete = False
0223
0224
0225
0226 _perConnection = False
0227
0228
0229 parentClass = None
0230 childClasses = {}
0231 childName = None
0232
0233
0234 dirty = False
0235
0236
0237 dbEncoding = None
0238
0239 __metaclass__ = declarative.DeclarativeMeta
0240
0241 def __classinit__(cls, new_attrs):
0242 for attr in cls._unshared_attributes:
0243 if attr not in new_attrs:
0244 setattr(cls, attr, None)
0245 declarative.setup_attributes(cls, new_attrs)
0246
0247 def __init__(self, instance):
0248 self.instance = weakref.proxy(instance)
0249
0250 @classmethod
0251 def send(cls, signal, *args, **kw):
0252 events.send(signal, cls.soClass, *args, **kw)
0253
0254 @classmethod
0255 def setClass(cls, soClass):
0256 cls.soClass = soClass
0257 if not cls.style:
0258 cls.style = styles.defaultStyle
0259 try:
0260 if cls.soClass._connection and cls.soClass._connection.style:
0261 cls.style = cls.soClass._connection.style
0262 except AttributeError:
0263 pass
0264 if cls.table is None:
0265 cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__)
0266 if cls.idName is None:
0267 cls.idName = cls.style.idForTable(cls.table)
0268
0269
0270
0271
0272
0273
0274 cls._plainSetters = {}
0275 cls._plainGetters = {}
0276 cls._plainForeignSetters = {}
0277 cls._plainForeignGetters = {}
0278 cls._plainJoinGetters = {}
0279 cls._plainJoinAdders = {}
0280 cls._plainJoinRemovers = {}
0281
0282
0283
0284 cls.columns = {}
0285 cls.columnList = []
0286
0287 cls.columnDefinitions = cls.columnDefinitions.copy()
0288 cls.indexes = []
0289 cls.indexDefinitions = cls.indexDefinitions[:]
0290 cls.joins = []
0291 cls.joinDefinitions = cls.joinDefinitions[:]
0292
0293
0294
0295
0296
0297
0298
0299
0300
0301 @classmethod
0302 def addColumn(cls, columnDef, changeSchema=False, connection=None):
0303 post_funcs = []
0304 cls.send(events.AddColumnSignal, cls.soClass, connection,
0305 columnDef.name, columnDef, changeSchema, post_funcs)
0306 sqlmeta = cls
0307 soClass = cls.soClass
0308 del cls
0309 column = columnDef.withClass(soClass)
0310 name = column.name
0311 assert name != 'id', (
0312 "The 'id' column is implicit, and should not be defined as "
0313 "a column")
0314 assert name not in sqlmeta.columns, (
0315 "The class %s.%s already has a column %r (%r), you cannot "
0316 "add the column %r"
0317 % (soClass.__module__, soClass.__name__, name,
0318 sqlmeta.columnDefinitions[name], columnDef))
0319
0320
0321 parent_columns = []
0322 for base in soClass.__bases__:
0323 if hasattr(base, "sqlmeta"):
0324 parent_columns.extend(base.sqlmeta.columns.keys())
0325 if hasattr(soClass, name):
0326 assert (name in parent_columns) or (name == "childName"), (
0327 "The class %s.%s already has a variable or method %r, you cannot "
0328 "add the column %r"
0329 % (soClass.__module__, soClass.__name__, name, name))
0330 sqlmeta.columnDefinitions[name] = columnDef
0331 sqlmeta.columns[name] = column
0332
0333 sqlmeta.columnList.append(column)
0334
0335
0336
0337
0338
0339
0340
0341
0342 if sqlmeta.cacheValues:
0343
0344
0345 getter = eval('lambda self: self._SO_loadValue(%s)' % repr(instanceName(name)))
0346
0347 else:
0348
0349
0350
0351 getter = eval('lambda self: self._SO_getValue(%s)' % repr(name))
0352 setattr(soClass, rawGetterName(name), getter)
0353
0354
0355
0356
0357 if not hasattr(soClass, getterName(name)) or (name == 'childName'):
0358 setattr(soClass, getterName(name), getter)
0359 sqlmeta._plainGetters[name] = 1
0360
0361
0362
0363
0364
0365
0366
0367
0368
0369
0370 if not column.immutable:
0371
0372 setter = eval('lambda self, val: self._SO_setValue(%s, val, self.%s, self.%s)' % (repr(name), '_SO_from_python_%s' % name, '_SO_to_python_%s' % name))
0373 setattr(soClass, '_SO_from_python_%s' % name, column.from_python)
0374 setattr(soClass, '_SO_to_python_%s' % name, column.to_python)
0375 setattr(soClass, rawSetterName(name), setter)
0376
0377 if not hasattr(soClass, setterName(name)) or (name == 'childName'):
0378 setattr(soClass, setterName(name), setter)
0379
0380
0381
0382 sqlmeta._plainSetters[name] = 1
0383
0384
0385
0386
0387
0388
0389 if column.foreignKey:
0390
0391
0392
0393
0394 origName = column.origName
0395 if sqlmeta.cacheValues:
0396
0397
0398 getter = eval('lambda self: self._SO_foreignKey(self._SO_loadValue(%r), self._SO_class_%s, %s)' % (instanceName(name), column.foreignKey, column.refColumn and repr(column.refColumn)))
0399 else:
0400
0401 getter = eval('lambda self: self._SO_foreignKey(self._SO_getValue(%s), self._SO_class_%s, %s)' % (repr(name), column.foreignKey, column.refColumn and repr(column.refColumn)))
0402 setattr(soClass, rawGetterName(origName), getter)
0403
0404
0405 if not hasattr(soClass, getterName(origName)):
0406 setattr(soClass, getterName(origName), getter)
0407 sqlmeta._plainForeignGetters[origName] = 1
0408
0409 if not column.immutable:
0410
0411
0412 setter = eval('lambda self, val: setattr(self, %s, self._SO_getID(val, %s))' % (repr(name), column.refColumn and repr(column.refColumn)))
0413 setattr(soClass, rawSetterName(origName), setter)
0414 if not hasattr(soClass, setterName(origName)):
0415 setattr(soClass, setterName(origName), setter)
0416 sqlmeta._plainForeignSetters[origName] = 1
0417
0418 classregistry.registry(sqlmeta.registry).addClassCallback(
0419 column.foreignKey,
0420 lambda foreign, me, attr: setattr(me, attr, foreign),
0421 soClass, '_SO_class_%s' % column.foreignKey)
0422
0423 if column.alternateMethodName:
0424 func = eval('lambda cls, val, connection=None: cls._SO_fetchAlternateID(%s, %s, val, connection=connection)' % (repr(column.name), repr(column.dbName)))
0425 setattr(soClass, column.alternateMethodName, classmethod(func))
0426
0427 if changeSchema:
0428 conn = connection or soClass._connection
0429 conn.addColumn(sqlmeta.table, column)
0430
0431 if soClass._SO_finishedClassCreation:
0432 makeProperties(soClass)
0433
0434 for func in post_funcs:
0435 func(soClass, column)
0436
0437 @classmethod
0438 def addColumnsFromDatabase(sqlmeta, connection=None):
0439 soClass = sqlmeta.soClass
0440 conn = connection or soClass._connection
0441 for columnDef in conn.columnsFromSchema(sqlmeta.table, soClass):
0442 if columnDef.name not in sqlmeta.columnDefinitions:
0443 if isinstance(columnDef.name, unicode):
0444 columnDef.name = columnDef.name.encode('ascii')
0445 sqlmeta.addColumn(columnDef)
0446
0447 @classmethod
0448 def delColumn(cls, column, changeSchema=False, connection=None):
0449 sqlmeta = cls
0450 soClass = sqlmeta.soClass
0451 if isinstance(column, str):
0452 if column in sqlmeta.columns:
0453 column = sqlmeta.columns[column]
0454 elif column+'ID' in sqlmeta.columns:
0455 column = sqlmeta.columns[column+'ID']
0456 else:
0457 raise ValueError('Unknown column ' + column)
0458 if isinstance(column, col.Col):
0459 for c in sqlmeta.columns.values():
0460 if column is c.columnDef:
0461 column = c
0462 break
0463 else:
0464 raise IndexError(
0465 "Column with definition %r not found" % column)
0466 post_funcs = []
0467 cls.send(events.DeleteColumnSignal, cls.soClass, connection,
0468 column.name, column, post_funcs)
0469 name = column.name
0470 del sqlmeta.columns[name]
0471 del sqlmeta.columnDefinitions[name]
0472 sqlmeta.columnList.remove(column)
0473 delattr(soClass, rawGetterName(name))
0474 if name in sqlmeta._plainGetters:
0475 delattr(soClass, getterName(name))
0476 delattr(soClass, rawSetterName(name))
0477 if name in sqlmeta._plainSetters:
0478 delattr(soClass, setterName(name))
0479 if column.foreignKey:
0480 delattr(soClass, rawGetterName(soClass.sqlmeta.style.instanceIDAttrToAttr(name)))
0481 if name in sqlmeta._plainForeignGetters:
0482 delattr(soClass, getterName(name))
0483 delattr(soClass, rawSetterName(soClass.sqlmeta.style.instanceIDAttrToAttr(name)))
0484 if name in sqlmeta._plainForeignSetters:
0485 delattr(soClass, setterName(name))
0486 if column.alternateMethodName:
0487 delattr(soClass, column.alternateMethodName)
0488
0489 if changeSchema:
0490 conn = connection or soClass._connection
0491 conn.delColumn(sqlmeta, column)
0492
0493 if soClass._SO_finishedClassCreation:
0494 unmakeProperties(soClass)
0495 makeProperties(soClass)
0496
0497 for func in post_funcs:
0498 func(soClass, column)
0499
0500
0501
0502
0503
0504 @classmethod
0505 def addJoin(cls, joinDef):
0506 sqlmeta = cls
0507 soClass = cls.soClass
0508
0509
0510
0511 join = joinDef.withClass(soClass)
0512 meth = join.joinMethodName
0513
0514 sqlmeta.joins.append(join)
0515 index = len(sqlmeta.joins)-1
0516 if joinDef not in sqlmeta.joinDefinitions:
0517 sqlmeta.joinDefinitions.append(joinDef)
0518
0519
0520
0521
0522 func = eval('lambda self: self.sqlmeta.joins[%i].performJoin(self)' % index)
0523
0524
0525 setattr(soClass, rawGetterName(meth), func)
0526 if not hasattr(soClass, getterName(meth)):
0527 setattr(soClass, getterName(meth), func)
0528 sqlmeta._plainJoinGetters[meth] = 1
0529
0530
0531
0532 if hasattr(join, 'remove'):
0533
0534
0535 func = eval('lambda self, obj: self.sqlmeta.joins[%i].remove(self, obj)' % index)
0536 setattr(soClass, '_SO_remove' + join.addRemoveName, func)
0537 if not hasattr(soClass, 'remove' + join.addRemoveName):
0538 setattr(soClass, 'remove' + join.addRemoveName, func)
0539 sqlmeta._plainJoinRemovers[meth] = 1
0540
0541
0542 if hasattr(join, 'add'):
0543
0544 func = eval('lambda self, obj: self.sqlmeta.joins[%i].add(self, obj)' % index)
0545 setattr(soClass, '_SO_add' + join.addRemoveName, func)
0546 if not hasattr(soClass, 'add' + join.addRemoveName):
0547 setattr(soClass, 'add' + join.addRemoveName, func)
0548 sqlmeta._plainJoinAdders[meth] = 1
0549
0550 if soClass._SO_finishedClassCreation:
0551 makeProperties(soClass)
0552
0553 @classmethod
0554 def delJoin(sqlmeta, joinDef):
0555 soClass = sqlmeta.soClass
0556 for join in sqlmeta.joins:
0557
0558
0559 if join is None:
0560 continue
0561 if joinDef is join.joinDef:
0562 break
0563 else:
0564 raise IndexError(
0565 "Join %r not found in class %r (from %r)"
0566 % (joinDef, soClass, sqlmeta.joins))
0567 meth = join.joinMethodName
0568 sqlmeta.joinDefinitions.remove(joinDef)
0569 for i in range(len(sqlmeta.joins)):
0570 if sqlmeta.joins[i] is join:
0571
0572
0573 sqlmeta.joins[i] = None
0574 delattr(soClass, rawGetterName(meth))
0575 if meth in sqlmeta._plainJoinGetters:
0576 delattr(soClass, getterName(meth))
0577 if hasattr(join, 'remove'):
0578 delattr(soClass, '_SO_remove' + join.addRemovePrefix)
0579 if meth in sqlmeta._plainJoinRemovers:
0580 delattr(soClass, 'remove' + join.addRemovePrefix)
0581 if hasattr(join, 'add'):
0582 delattr(soClass, '_SO_add' + join.addRemovePrefix)
0583 if meth in sqlmeta._plainJoinAdders:
0584 delattr(soClass, 'add' + join.addRemovePrefix)
0585
0586 if soClass._SO_finishedClassCreation:
0587 unmakeProperties(soClass)
0588 makeProperties(soClass)
0589
0590
0591
0592
0593
0594 @classmethod
0595 def addIndex(cls, indexDef):
0596 cls.indexDefinitions.append(indexDef)
0597 index = indexDef.withClass(cls.soClass)
0598 cls.indexes.append(index)
0599 setattr(cls.soClass, index.name, index)
0600
0601
0602
0603
0604
0605 @classmethod
0606 def getColumns(sqlmeta):
0607 return sqlmeta.columns.copy()
0608
0609 def asDict(self):
0610 """
0611 Return the object as a dictionary of columns to values.
0612 """
0613 result = {}
0614 for key in self.getColumns():
0615 result[key] = getattr(self.instance, key)
0616 result['id'] = self.instance.id
0617 return result
0618
0619 @classmethod
0620 def expireAll(sqlmeta, connection=None):
0621 """
0622 Expire all instances of this class.
0623 """
0624 soClass = sqlmeta.soClass
0625 connection = connection or soClass._connection
0626 cache_set = connection.cache
0627 cache_set.weakrefAll(soClass)
0628 for item in cache_set.getAll(soClass):
0629 item.expire()
0630
0631
0632sqlhub = dbconnection.ConnectionHub()
0633
0634
0635
0636
0637
0638warnings_level = 1
0639exception_level = None
0640
0641
0642
0643
0644
0645def deprecated(message, level=1, stacklevel=2):
0646 if exception_level is not None and exception_level <= level:
0647 raise NotImplementedError(message)
0648 if warnings_level is not None and warnings_level <= level:
0649 warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
0650
0651
0652
0653
0654def setDeprecationLevel(warning=1, exception=None):
0655 """
0656 Set the deprecation level for SQLObject. Low levels are more
0657 actively being deprecated. Any warning at a level at or below
0658 ``warning`` will give a warning. Any warning at a level at or
0659 below ``exception`` will give an exception. You can use a higher
0660 ``exception`` level for tests to help upgrade your code. ``None``
0661 for either value means never warn or raise exceptions.
0662
0663 The levels currently mean:
0664
0665 1) Deprecated in current version. Will be removed in next version.
0666
0667 2) Planned to deprecate in next version, remove later.
0668
0669 3) Planned to deprecate sometime, remove sometime much later.
0670
0671 As the SQLObject versions progress, the deprecation level of
0672 specific features will go down, indicating the advancing nature of
0673 the feature's doom. We'll try to keep features at 1 for a major
0674 revision.
0675
0676 As time continues there may be a level 0, which will give a useful
0677 error message (better than ``AttributeError``) but where the
0678 feature has been fully removed.
0679 """
0680 global warnings_level, exception_level
0681 warnings_level = warning
0682 exception_level = exception
0683
0684
0685class _sqlmeta_attr(object):
0686
0687 def __init__(self, name, deprecation_level):
0688 self.name = name
0689 self.deprecation_level = deprecation_level
0690
0691 def __get__(self, obj, type=None):
0692 if self.deprecation_level is not None:
0693 deprecated(
0694 'Use of this attribute should be replaced with '
0695 '.sqlmeta.%s' % self.name, level=self.deprecation_level)
0696 return getattr((type or obj).sqlmeta, self.name)
0697
0698
0699_postponed_local = local()
0700
0701
0702
0703
0704
0705
0706
0707class SQLObject(object):
0708
0709 __metaclass__ = declarative.DeclarativeMeta
0710
0711 _connection = sqlhub
0712
0713 sqlmeta = sqlmeta
0714
0715
0716
0717 _inheritable = False
0718 _parent = None
0719 childName = None
0720
0721
0722 SelectResultsClass = SelectResults
0723
0724 def __classinit__(cls, new_attrs):
0725
0726
0727
0728 is_base = cls.__bases__ == (object,)
0729
0730 cls._SO_setupSqlmeta(new_attrs, is_base)
0731
0732 implicitColumns = _collectAttributes(cls, new_attrs, col.Col)
0733 implicitJoins = _collectAttributes(cls, new_attrs, joins.Join)
0734 implicitIndexes = _collectAttributes(cls, new_attrs, index.DatabaseIndex)
0735
0736 if not is_base:
0737 cls._SO_cleanDeprecatedAttrs(new_attrs)
0738
0739 if '_connection' in new_attrs:
0740 connection = new_attrs['_connection']
0741 del cls._connection
0742 assert 'connection' not in new_attrs
0743 elif 'connection' in new_attrs:
0744 connection = new_attrs['connection']
0745 del cls.connection
0746 else:
0747 connection = None
0748
0749 cls._SO_finishedClassCreation = False
0750
0751
0752
0753
0754 if not connection and not getattr(cls, '_connection', None):
0755 mod = sys.modules[cls.__module__]
0756
0757
0758 if hasattr(mod, '__connection__'):
0759 connection = mod.__connection__
0760
0761
0762
0763
0764 if connection and ('_connection' not in cls.__dict__):
0765 cls.setConnection(connection)
0766
0767 sqlmeta = cls.sqlmeta
0768
0769
0770
0771
0772
0773 for key in sqlmeta.columnDefinitions.keys():
0774 if (key in new_attrs
0775 and new_attrs[key] is None):
0776 del sqlmeta.columnDefinitions[key]
0777
0778 for column in sqlmeta.columnDefinitions.values():
0779 sqlmeta.addColumn(column)
0780
0781 for column in implicitColumns:
0782 sqlmeta.addColumn(column)
0783
0784
0785
0786 declarative.setup_attributes(cls, new_attrs)
0787
0788 if sqlmeta.fromDatabase:
0789 sqlmeta.addColumnsFromDatabase()
0790
0791 for j in implicitJoins:
0792 sqlmeta.addJoin(j)
0793 for i in implicitIndexes:
0794 sqlmeta.addIndex(i)
0795
0796 order_getter = lambda o: o.creationOrder
0797 sqlmeta.columnList.sort(key=order_getter)
0798 sqlmeta.indexes.sort(key=order_getter)
0799 sqlmeta.indexDefinitions.sort(key=order_getter)
0800
0801
0802
0803 sqlmeta.joinDefinitions.sort(key=order_getter)
0804
0805
0806
0807 cls._notifyFinishClassCreation()
0808 cls._SO_finishedClassCreation = True
0809 makeProperties(cls)
0810
0811
0812
0813
0814 if not is_base:
0815 cls.q = sqlbuilder.SQLObjectTable(cls)
0816 cls.j = sqlbuilder.SQLObjectTableWithJoins(cls)
0817
0818 classregistry.registry(sqlmeta.registry).addClass(cls)
0819
0820 @classmethod
0821 def _SO_setupSqlmeta(cls, new_attrs, is_base):
0822 """
0823 This fixes up the sqlmeta attribute. It handles both the case
0824 where no sqlmeta was given (in which we need to create another
0825 subclass), or the sqlmeta given doesn't have the proper
0826 inheritance. Lastly it calls sqlmeta.setClass, which handles
0827 much of the setup.
0828 """
0829 if ('sqlmeta' not in new_attrs
0830 and not is_base):
0831
0832
0833 cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {})
0834 if not issubclass(cls.sqlmeta, sqlmeta):
0835
0836
0837
0838 assert cls.sqlmeta.__bases__ in ((), (object,)), (
0839 "If you do not inherit your sqlmeta class from "
0840 "sqlobject.sqlmeta, it must not inherit from any other "
0841 "class (your sqlmeta inherits from: %s)"
0842 % cls.sqlmeta.__bases__)
0843 for base in cls.__bases__:
0844 superclass = getattr(base, 'sqlmeta', None)
0845 if superclass:
0846 break
0847 else:
0848 assert 0, (
0849 "No sqlmeta class could be found in any superclass "
0850 "(while fixing up sqlmeta %r inheritance)"
0851 % cls.sqlmeta)
0852 values = dict(cls.sqlmeta.__dict__)
0853 for key in values.keys():
0854 if key.startswith('__') and key.endswith('__'):
0855
0856 del values[key]
0857 cls.sqlmeta = type('sqlmeta', (superclass,), values)
0858
0859 if not is_base:
0860 cls.sqlmeta.setClass(cls)
0861
0862 @classmethod
0863 def _SO_cleanDeprecatedAttrs(cls, new_attrs):
0864 """
0865 This removes attributes on SQLObject subclasses that have
0866 been deprecated; they are moved to the sqlmeta class, and
0867 a deprecation warning is given.
0868 """
0869 for attr in ():
0870 if attr in new_attrs:
0871 deprecated("%r is deprecated and read-only; please do "
0872 "not use it in your classes until it is fully "
0873 "deprecated" % attr, level=1, stacklevel=5)
0874
0875 @classmethod
0876 def get(cls, id, connection=None, selectResults=None):
0877
0878 assert id is not None, 'None is not a possible id for %s' % cls.__name__
0879
0880 id = cls.sqlmeta.idType(id)
0881
0882 if connection is None:
0883 cache = cls._connection.cache
0884 else:
0885 cache = connection.cache
0886
0887
0888
0889 val = cache.get(id, cls)
0890 if val is None:
0891 try:
0892 val = cls(_SO_fetch_no_create=1)
0893 val._SO_validatorState = sqlbuilder.SQLObjectState(val)
0894 val._init(id, connection, selectResults)
0895 cache.put(id, cls, val)
0896 finally:
0897 cache.finishPut(cls)
0898 elif selectResults and not val.sqlmeta.dirty:
0899 val._SO_writeLock.acquire()
0900 try:
0901 val._SO_selectInit(selectResults)
0902 val.sqlmeta.expired = False
0903 finally:
0904 val._SO_writeLock.release()
0905 return val
0906
0907 @classmethod
0908 def _notifyFinishClassCreation(cls):
0909 pass
0910
0911 def _init(self, id, connection=None, selectResults=None):
0912 assert id is not None
0913
0914
0915
0916 self.id = id
0917 self._SO_writeLock = threading.Lock()
0918
0919
0920
0921
0922 if (connection is not None) and (getattr(self, '_connection', None) is not connection):
0924 self._connection = connection
0925
0926
0927
0928 self.sqlmeta._perConnection = True
0929
0930 if not selectResults:
0931 dbNames = [col.dbName for col in self.sqlmeta.columnList]
0932 selectResults = self._connection._SO_selectOne(self, dbNames)
0933 if not selectResults:
0934 raise SQLObjectNotFound, "The object %s by the ID %s does not exist" % (self.__class__.__name__, self.id)
0935 self._SO_selectInit(selectResults)
0936 self._SO_createValues = {}
0937 self.sqlmeta.dirty = False
0938
0939 def _SO_loadValue(self, attrName):
0940 try:
0941 return getattr(self, attrName)
0942 except AttributeError:
0943 try:
0944 self._SO_writeLock.acquire()
0945 try:
0946
0947
0948
0949
0950
0951
0952 result = getattr(self, attrName)
0953 except AttributeError:
0954 pass
0955 else:
0956 return result
0957 self.sqlmeta.expired = False
0958 dbNames = [col.dbName for col in self.sqlmeta.columnList]
0959 selectResults = self._connection._SO_selectOne(self, dbNames)
0960 if not selectResults:
0961 raise SQLObjectNotFound, "The object %s by the ID %s has been deleted" % (self.__class__.__name__, self.id)
0962 self._SO_selectInit(selectResults)
0963 result = getattr(self, attrName)
0964 return result
0965 finally:
0966 self._SO_writeLock.release()
0967
0968 def sync(self):
0969 if self.sqlmeta.lazyUpdate and self._SO_createValues:
0970 self.syncUpdate()
0971 self._SO_writeLock.acquire()
0972 try:
0973 dbNames = [col.dbName for col in self.sqlmeta.columnList]
0974 selectResults = self._connection._SO_selectOne(self, dbNames)
0975 if not selectResults:
0976 raise SQLObjectNotFound, "The object %s by the ID %s has been deleted" % (self.__class__.__name__, self.id)
0977 self._SO_selectInit(selectResults)
0978 self.sqlmeta.expired = False
0979 finally:
0980 self._SO_writeLock.release()
0981
0982 def syncUpdate(self):
0983 if not self._SO_createValues:
0984 return
0985 self._SO_writeLock.acquire()
0986 try:
0987 if self.sqlmeta.columns:
0988 columns = self.sqlmeta.columns
0989 values = [(columns[v[0]].dbName, v[1])
0990 for v in sorted(
0991 self._SO_createValues.items(),
0992 key=lambda c: columns[c[0]].creationOrder)]
0993 self._connection._SO_update(self, values)
0994 self.sqlmeta.dirty = False
0995 self._SO_createValues = {}
0996 finally:
0997 self._SO_writeLock.release()
0998
0999 post_funcs = []
1000 self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1001 for func in post_funcs:
1002 func(self)
1003
1004 def expire(self):
1005 if self.sqlmeta.expired:
1006 return
1007 self._SO_writeLock.acquire()
1008 try:
1009 if self.sqlmeta.expired:
1010 return
1011 for column in self.sqlmeta.columnList:
1012 delattr(self, instanceName(column.name))
1013 self.sqlmeta.expired = True
1014 self._connection.cache.expire(self.id, self.__class__)
1015 self._SO_createValues = {}
1016 finally:
1017 self._SO_writeLock.release()
1018
1019 def _SO_setValue(self, name, value, from_python, to_python):
1020
1021
1022
1023
1024
1025
1026
1027 d = {name: value}
1028 if not self.sqlmeta._creating and not getattr(self.sqlmeta, "row_update_sig_suppress", False):
1029 self.sqlmeta.send(events.RowUpdateSignal, self, d)
1030 if len(d) != 1 or name not in d:
1031
1032
1033 self.sqlmeta.row_update_sig_suppress = True
1034 self.set(**d)
1035 del self.sqlmeta.row_update_sig_suppress
1036 value = d[name]
1037 if from_python:
1038 dbValue = from_python(value, self._SO_validatorState)
1039 else:
1040 dbValue = value
1041 if to_python:
1042 value = to_python(dbValue, self._SO_validatorState)
1043 if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1044 self.sqlmeta.dirty = True
1045 self._SO_createValues[name] = dbValue
1046 setattr(self, instanceName(name), value)
1047 return
1048
1049 self._connection._SO_update(
1050 self, [(self.sqlmeta.columns[name].dbName,
1051 dbValue)])
1052
1053 if self.sqlmeta.cacheValues:
1054 setattr(self, instanceName(name), value)
1055
1056 post_funcs = []
1057 self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1058 for func in post_funcs:
1059 func(self)
1060
1061 def set(self, _suppress_set_sig=False, **kw):
1062 if not self.sqlmeta._creating and not getattr(self.sqlmeta, "row_update_sig_suppress", False) and not _suppress_set_sig:
1063 self.sqlmeta.send(events.RowUpdateSignal, self, kw)
1064
1065
1066
1067
1068
1069
1070 is_column = lambda _c: _c in self.sqlmeta._plainSetters
1071 f_is_column = lambda item: is_column(item[0])
1072 f_not_column = lambda item: not is_column(item[0])
1073 items = kw.items()
1074 extra = dict(filter(f_not_column, items))
1075 kw = dict(filter(f_is_column, items))
1076
1077
1078 if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1079 for name, value in kw.items():
1080 from_python = getattr(self, '_SO_from_python_%s' % name, None)
1081 if from_python:
1082 kw[name] = dbValue = from_python(value, self._SO_validatorState)
1083 else:
1084 dbValue = value
1085 to_python = getattr(self, '_SO_to_python_%s' % name, None)
1086 if to_python:
1087 value = to_python(dbValue, self._SO_validatorState)
1088 setattr(self, instanceName(name), value)
1089
1090 self._SO_createValues.update(kw)
1091
1092 for name, value in extra.items():
1093 try:
1094 getattr(self.__class__, name)
1095 except AttributeError:
1096 if name not in self.sqlmeta.columns:
1097 raise TypeError, "%s.set() got an unexpected keyword argument %s" % (self.__class__.__name__, name)
1098 try:
1099 setattr(self, name, value)
1100 except AttributeError, e:
1101 raise AttributeError, '%s (with attribute %r)' % (e, name)
1102
1103 self.sqlmeta.dirty = True
1104 return
1105
1106 self._SO_writeLock.acquire()
1107
1108 try:
1109
1110
1111
1112
1113
1114
1115
1116
1117 toUpdate = {}
1118 for name, value in kw.items():
1119 from_python = getattr(self, '_SO_from_python_%s' % name, None)
1120 if from_python:
1121 dbValue = from_python(value, self._SO_validatorState)
1122 else:
1123 dbValue = value
1124 to_python = getattr(self, '_SO_to_python_%s' % name, None)
1125 if to_python:
1126 value = to_python(dbValue, self._SO_validatorState)
1127 if self.sqlmeta.cacheValues:
1128 setattr(self, instanceName(name), value)
1129 toUpdate[name] = dbValue
1130 for name, value in extra.items():
1131 try:
1132 getattr(self.__class__, name)
1133 except AttributeError:
1134 if name not in self.sqlmeta.columns:
1135 raise TypeError, "%s.set() got an unexpected keyword argument %s" % (self.__class__.__name__, name)
1136 try:
1137 setattr(self, name, value)
1138 except AttributeError, e:
1139 raise AttributeError, '%s (with attribute %r)' % (e, name)
1140
1141 if toUpdate:
1142 toUpdate = toUpdate.items()
1143 toUpdate.sort(key=lambda c: self.sqlmeta.columns[c[0]].creationOrder)
1144 args = [(self.sqlmeta.columns[name].dbName, value)
1145 for name, value in toUpdate]
1146 self._connection._SO_update(self, args)
1147 finally:
1148 self._SO_writeLock.release()
1149
1150 post_funcs = []
1151 self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1152 for func in post_funcs:
1153 func(self)
1154
1155 def _SO_selectInit(self, row):
1156 for col, colValue in zip(self.sqlmeta.columnList, row):
1157 if col.to_python:
1158 colValue = col.to_python(colValue, self._SO_validatorState)
1159 setattr(self, instanceName(col.name), colValue)
1160
1161 def _SO_getValue(self, name):
1162
1163 assert not self.sqlmeta._obsolete, (
1164 "%s with id %s has become obsolete" % (self.__class__.__name__, self.id))
1166
1167
1168 column = self.sqlmeta.columns[name]
1169 results = self._connection._SO_selectOne(self, [column.dbName])
1170
1171 assert results != None, "%s with id %s is not in the database" % (self.__class__.__name__, self.id)
1173 value = results[0]
1174 if column.to_python:
1175 value = column.to_python(value, self._SO_validatorState)
1176 return value
1177
1178 def _SO_foreignKey(self, value, joinClass, idName=None):
1179 if value is None:
1180 return None
1181 if self.sqlmeta._perConnection:
1182 connection = self._connection
1183 else:
1184 connection = None
1185 if idName is None:
1186 return joinClass.get(value, connection=connection)
1187 return joinClass.select(
1188 getattr(joinClass.q, idName)==value, connection=connection).getOne()
1189
1190 def __init__(self, **kw):
1191
1192
1193
1194
1195 try:
1196 _postponed_local.postponed_calls
1197 postponed_created = False
1198 except AttributeError:
1199 _postponed_local.postponed_calls = []
1200 postponed_created = True
1201
1202 try:
1203
1204
1205
1206 self.sqlmeta = self.__class__.sqlmeta(self)
1207
1208
1209
1210 if '_SO_fetch_no_create' in kw:
1211 return
1212
1213 post_funcs = []
1214 self.sqlmeta.send(events.RowCreateSignal, self, kw, post_funcs)
1215
1216
1217 if 'connection' in kw:
1218 connection = kw.pop('connection')
1219 if getattr(self, '_connection', None) is not connection:
1220 self._connection = connection
1221 self.sqlmeta._perConnection = True
1222
1223 self._SO_writeLock = threading.Lock()
1224
1225 if 'id' in kw:
1226 id = self.sqlmeta.idType(kw['id'])
1227 del kw['id']
1228 else:
1229 id = None
1230
1231 self._create(id, **kw)
1232
1233 for func in post_funcs:
1234 func(self)
1235 finally:
1236
1237
1238
1239 if postponed_created:
1240 try:
1241 for func in _postponed_local.postponed_calls:
1242 func()
1243 finally:
1244 del _postponed_local.postponed_calls
1245
1246 def _create(self, id, **kw):
1247
1248 self.sqlmeta._creating = True
1249 self._SO_createValues = {}
1250 self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1251
1252
1253
1254 for column in self.sqlmeta.columnList:
1255
1256
1257
1258 if column.name not in kw and column.foreignName not in kw:
1259 default = column.default
1260
1261
1262
1263 if default is NoDefault:
1264 if column.defaultSQL is None:
1265 raise TypeError, "%s() did not get expected keyword argument '%s'" % (self.__class__.__name__, column.name)
1266 else:
1267
1268
1269 continue
1270
1271
1272
1273
1274 kw[column.name] = default
1275
1276 self.set(**kw)
1277
1278
1279 self._SO_finishCreate(id)
1280
1281 def _SO_finishCreate(self, id=None):
1282
1283
1284
1285 setters = self._SO_createValues.items()
1286 setters.sort(key=lambda c: self.sqlmeta.columns[c[0]].creationOrder)
1287
1288 names = [self.sqlmeta.columns[v[0]].dbName for v in setters]
1289 values = [v[1] for v in setters]
1290
1291
1292
1293 self.sqlmeta.dirty = False
1294 if not self.sqlmeta.lazyUpdate:
1295 del self._SO_createValues
1296 else:
1297 self._SO_createValues = {}
1298 del self.sqlmeta._creating
1299
1300
1301
1302
1303 id = self._connection.queryInsertID(self,
1304 id, names, values)
1305 cache = self._connection.cache
1306 cache.created(id, self.__class__, self)
1307 self._init(id)
1308 post_funcs = []
1309 kw = dict([('class', self.__class__), ('id', id)])
1310 def _send_RowCreatedSignal():
1311 self.sqlmeta.send(events.RowCreatedSignal, self, kw, post_funcs)
1312 for func in post_funcs:
1313 func(self)
1314 _postponed_local.postponed_calls.append(_send_RowCreatedSignal)
1315
1316 def _SO_getID(self, obj, refColumn=None):
1317 return getID(obj, refColumn)
1318
1319 @classmethod
1320 def _findAlternateID(cls, name, dbName, value, connection=None):
1321 if isinstance(name, str):
1322 name = (name,)
1323 value = (value,)
1324 if len(name) != len(value):
1325 raise ValueError, "'column' and 'value' tuples must be of the same size"
1326 new_value = []
1327 for n, v in zip(name, value):
1328 from_python = getattr(cls, '_SO_from_python_' + n)
1329 if from_python:
1330 v = from_python(v, sqlbuilder.SQLObjectState(cls, connection=connection))
1331 new_value.append(v)
1332 condition = sqlbuilder.AND(*[getattr(cls.q, n)==v for n,v in zip(name, new_value)])
1333 return (connection or cls._connection)._SO_selectOneAlt(
1334 cls,
1335 [cls.sqlmeta.idName] +
1336 [column.dbName for column in cls.sqlmeta.columnList],
1337 condition), None
1338
1339 @classmethod
1340 def _SO_fetchAlternateID(cls, name, dbName, value, connection=None, idxName=None):
1341 result, obj = cls._findAlternateID(name, dbName, value, connection)
1342 if not result:
1343 if idxName is None:
1344 raise SQLObjectNotFound, "The %s by alternateID %s = %s does not exist" % (cls.__name__, name, repr(value))
1345 else:
1346 names = []
1347 for i in xrange(len(name)):
1348 names.append("%s = %s" % (name[i], repr(value[i])))
1349 names = ', '.join(names)
1350 raise SQLObjectNotFound, "The %s by unique index %s(%s) does not exist" % (cls.__name__, idxName, names)
1351 if obj:
1352 return obj
1353 if connection:
1354 obj = cls.get(result[0], connection=connection, selectResults=result[1:])
1355 else:
1356 obj = cls.get(result[0], selectResults=result[1:])
1357 return obj
1358
1359 @classmethod
1360 def _SO_depends(cls):
1361 return findDependencies(cls.__name__, cls.sqlmeta.registry)
1362
1363 @classmethod
1364 def select(cls, clause=None, clauseTables=None,
1365 orderBy=NoDefault, limit=None,
1366 lazyColumns=False, reversed=False,
1367 distinct=False, connection=None,
1368 join=None, forUpdate=False):
1369 return cls.SelectResultsClass(cls, clause,
1370 clauseTables=clauseTables,
1371 orderBy=orderBy,
1372 limit=limit,
1373 lazyColumns=lazyColumns,
1374 reversed=reversed,
1375 distinct=distinct,
1376 connection=connection,
1377 join=join, forUpdate=forUpdate)
1378
1379 @classmethod
1380 def selectBy(cls, connection=None, **kw):
1381 conn = connection or cls._connection
1382 return cls.SelectResultsClass(cls,
1383 conn._SO_columnClause(cls, kw),
1384 connection=conn)
1385
1386 @classmethod
1387 def tableExists(cls, connection=None):
1388 conn = connection or cls._connection
1389 return conn.tableExists(cls.sqlmeta.table)
1390
1391 @classmethod
1392 def dropTable(cls, ifExists=False, dropJoinTables=True, cascade=False,
1393 connection=None):
1394 conn = connection or cls._connection
1395 if ifExists and not cls.tableExists(connection=conn):
1396 return
1397 extra_sql = []
1398 post_funcs = []
1399 cls.sqlmeta.send(events.DropTableSignal, cls, connection,
1400 extra_sql, post_funcs)
1401 conn.dropTable(cls.sqlmeta.table, cascade)
1402 if dropJoinTables:
1403 cls.dropJoinTables(ifExists=ifExists, connection=conn)
1404 for sql in extra_sql:
1405 connection.query(sql)
1406 for func in post_funcs:
1407 func(cls, conn)
1408
1409 @classmethod
1410 def createTable(cls, ifNotExists=False, createJoinTables=True,
1411 createIndexes=True, applyConstraints=True,
1412 connection=None):
1413 conn = connection or cls._connection
1414 if ifNotExists and cls.tableExists(connection=conn):
1415 return
1416 extra_sql = []
1417 post_funcs = []
1418 cls.sqlmeta.send(events.CreateTableSignal, cls, connection,
1419 extra_sql, post_funcs)
1420 constraints = conn.createTable(cls)
1421 if applyConstraints:
1422 for constraint in constraints:
1423 conn.query(constraint)
1424 else:
1425 extra_sql.extend(constraints)
1426 if createJoinTables:
1427 cls.createJoinTables(ifNotExists=ifNotExists,
1428 connection=conn)
1429 if createIndexes:
1430 cls.createIndexes(ifNotExists=ifNotExists,
1431 connection=conn)
1432 for func in post_funcs:
1433 func(cls, conn)
1434 return extra_sql
1435
1436 @classmethod
1437 def createTableSQL(cls, createJoinTables=True, createIndexes=True,
1438 connection=None):
1439 conn = connection or cls._connection
1440 sql, constraints = conn.createTableSQL(cls)
1441 if createJoinTables:
1442 join_sql = cls.createJoinTablesSQL(connection=conn)
1443 if join_sql:
1444 sql += ';\n' + join_sql
1445 if createIndexes:
1446 index_sql = cls.createIndexesSQL(connection=conn)
1447 if index_sql:
1448 sql += ';\n' + index_sql
1449 return sql, constraints
1450
1451 @classmethod
1452 def createJoinTables(cls, ifNotExists=False, connection=None):
1453 conn = connection or cls._connection
1454 for join in cls._getJoinsToCreate():
1455 if (ifNotExists and
1456 conn.tableExists(join.intermediateTable)):
1457 continue
1458 conn._SO_createJoinTable(join)
1459
1460 @classmethod
1461 def createJoinTablesSQL(cls, connection=None):
1462 conn = connection or cls._connection
1463 sql = []
1464 for join in cls._getJoinsToCreate():
1465 sql.append(conn._SO_createJoinTableSQL(join))
1466 return ';\n'.join(sql)
1467
1468 @classmethod
1469 def createIndexes(cls, ifNotExists=False, connection=None):
1470 conn = connection or cls._connection
1471 for index in cls.sqlmeta.indexes:
1472 if not index:
1473 continue
1474 conn._SO_createIndex(cls, index)
1475
1476 @classmethod
1477 def createIndexesSQL(cls, connection=None):
1478 conn = connection or cls._connection
1479 sql = []
1480 for index in cls.sqlmeta.indexes:
1481 if not index:
1482 continue
1483 sql.append(conn.createIndexSQL(cls, index))
1484 return ';\n'.join(sql)
1485
1486 @classmethod
1487 def _getJoinsToCreate(cls):
1488 joins = []
1489 for join in cls.sqlmeta.joins:
1490 if not join:
1491 continue
1492 if not join.hasIntermediateTable() or not getattr(join, 'createRelatedTable', True):
1493 continue
1494 if join.soClass.__name__ > join.otherClass.__name__:
1495 continue
1496 joins.append(join)
1497 return joins
1498
1499 @classmethod
1500 def dropJoinTables(cls, ifExists=False, connection=None):
1501 conn = connection or cls._connection
1502 for join in cls.sqlmeta.joins:
1503 if not join:
1504 continue
1505 if not join.hasIntermediateTable() or not getattr(join, 'createRelatedTable', True):
1506 continue
1507 if join.soClass.__name__ > join.otherClass.__name__:
1508 continue
1509 if ifExists and not conn.tableExists(join.intermediateTable):
1511 continue
1512 conn._SO_dropJoinTable(join)
1513
1514 @classmethod
1515 def clearTable(cls, connection=None, clearJoinTables=True):
1516
1517
1518 conn = connection or cls._connection
1519 conn.clearTable(cls.sqlmeta.table)
1520 if clearJoinTables:
1521 for join in cls._getJoinsToCreate():
1522 conn.clearTable(join.intermediateTable)
1523
1524 def destroySelf(self):
1525 post_funcs = []
1526 self.sqlmeta.send(events.RowDestroySignal, self, post_funcs)
1527
1528
1529 klass = self.__class__
1530
1531
1532 for join in klass.sqlmeta.joins:
1533 if isinstance(join, joins.SORelatedJoin):
1534 q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable, join.joinColumn, self.id)
1535 self._connection.query(q)
1536
1537 depends = []
1538 depends = self._SO_depends()
1539 for k in depends:
1540
1541 for join in k.sqlmeta.joins:
1542 if isinstance(join, joins.SORelatedJoin) and join.otherClassName == klass.__name__:
1543 q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable, join.otherColumn, self.id)
1544 self._connection.query(q)
1545
1546 cols = findDependantColumns(klass.__name__, k)
1547
1548
1549 if len(cols) == 0:
1550 continue
1551
1552 query = []
1553 delete = setnull = restrict = False
1554 for col in cols:
1555 if col.cascade == False:
1556
1557 restrict = True
1558 query.append(getattr(k.q, col.name) == self.id)
1559 if col.cascade == 'null':
1560 setnull = col.name
1561 elif col.cascade:
1562 delete = True
1563 assert delete or setnull or restrict, (
1564 "Class %s depends on %s accoriding to "
1565 "findDependantColumns, but this seems inaccurate"
1566 % (k, klass))
1567 query = sqlbuilder.OR(*query)
1568 results = k.select(query, connection=self._connection)
1569 if restrict:
1570 if results.count():
1571
1572
1573 raise SQLObjectIntegrityError, (
1574 "Tried to delete %s::%s but "
1575 "table %s has a restriction against it" %
1576 (klass.__name__, self.id, k.__name__))
1577 else:
1578 for row in results:
1579 if delete:
1580 row.destroySelf()
1581 else:
1582 row.set(**{setnull: None})
1583
1584 self.sqlmeta._obsolete = True
1585 self._connection._SO_delete(self)
1586 self._connection.cache.expire(self.id, self.__class__)
1587
1588 for func in post_funcs:
1589 func(self)
1590
1591 post_funcs = []
1592 self.sqlmeta.send(events.RowDestroyedSignal, self, post_funcs)
1593 for func in post_funcs:
1594 func(self)
1595
1596 @classmethod
1597 def delete(cls, id, connection=None):
1598 obj = cls.get(id, connection=connection)
1599 obj.destroySelf()
1600
1601 @classmethod
1602 def deleteMany(cls, where=NoDefault, connection=None):
1603 conn = connection or cls._connection
1604 conn.query(conn.sqlrepr(sqlbuilder.Delete(cls.sqlmeta.table, where)))
1605
1606 @classmethod
1607 def deleteBy(cls, connection=None, **kw):
1608 conn = connection or cls._connection
1609 conn.query(conn.sqlrepr(sqlbuilder.Delete(cls.sqlmeta.table,
1610 conn._SO_columnClause(cls, kw))))
1611
1612 def __repr__(self):
1613 if not hasattr(self, 'id'):
1614
1615 return '<%s (not initialized)>' % self.__class__.__name__
1616 return '<%s %r %s>' % (self.__class__.__name__,
1618 self.id,
1619 ' '.join(['%s=%s' % (name, repr(value)) for name, value in self._reprItems()]))
1620
1621 def __sqlrepr__(self, db):
1622 return str(self.id)
1623
1624 @classmethod
1625 def sqlrepr(cls, value, connection=None):
1626 return (connection or cls._connection).sqlrepr(value)
1627
1628 @classmethod
1629 def coerceID(cls, value):
1630 if isinstance(value, cls):
1631 return value.id
1632 else:
1633 return cls.sqlmeta.idType(value)
1634
1635 def _reprItems(self):
1636 items = []
1637 for col in self.sqlmeta.columnList:
1638 value = getattr(self, col.name)
1639 r = repr(value)
1640 if len(r) > 20:
1641 value = r[:17] + "..." + r[-1]
1642 items.append((col.name, value))
1643 return items
1644
1645 @classmethod
1646 def setConnection(cls, value):
1647 if isinstance(value, basestring):
1648 value = dbconnection.connectionForURI(value)
1649 cls._connection = value
1650
1651 def tablesUsedImmediate(self):
1652 return [self.__class__.q]
1653
1654
1655
1656
1657 def __eq__(self, other):
1658 if self.__class__ is other.__class__:
1659 if self.id == other.id:
1660 return True
1661 return False
1662
1663 def __ne__(self, other):
1664 return not self.__eq__(other)
1665
1666 def __lt__(self, other):
1667 return NotImplemented
1668
1669 def __le__(self, other):
1670 return NotImplemented
1671
1672 def __gt__(self, other):
1673 return NotImplemented
1674
1675 def __ge__(self, other):
1676 return NotImplemented
1677
1678
1679
1680
1681 def __getstate__(self):
1682 if self.sqlmeta._perConnection:
1683 from pickle import PicklingError
1684 raise PicklingError('Cannot pickle an SQLObject instance that has a per-instance connection')
1685 if self.sqlmeta.lazyUpdate and self._SO_createValues:
1686 self.syncUpdate()
1687 d = self.__dict__.copy()
1688 del d['sqlmeta']
1689 del d['_SO_validatorState']
1690 del d['_SO_writeLock']
1691 del d['_SO_createValues']
1692 return d
1693
1694 def __setstate__(self, d):
1695 self.__init__(_SO_fetch_no_create=1)
1696 self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1697 self._SO_writeLock = threading.Lock()
1698 self._SO_createValues = {}
1699 self.__dict__.update(d)
1700 cls = self.__class__
1701 cache = self._connection.cache
1702 if cache.tryGet(self.id, cls) is not None:
1703 raise ValueError(
1704 "Cannot unpickle %s row with id=%s - a different instance with the id already exists in the cache" % (cls.__name__, self.id))
1705 cache.created(self.id, cls, self)
1706
1707
1708def setterName(name):
1709 return '_set_%s' % name
1710def rawSetterName(name):
1711 return '_SO_set_%s' % name
1712def getterName(name):
1713 return '_get_%s' % name
1714def rawGetterName(name):
1715 return '_SO_get_%s' % name
1716def instanceName(name):
1717 return '_SO_val_%s' % name
1718
1719
1720
1721
1722
1723
1724def getID(obj, refColumn=None):
1725 if isinstance(obj, SQLObject):
1726 return getattr(obj, refColumn or 'id')
1727 elif isinstance(obj, int):
1728 return obj
1729 elif isinstance(obj, long):
1730 return int(obj)
1731 elif isinstance(obj, str):
1732 try:
1733 return int(obj)
1734 except ValueError:
1735 return obj
1736 elif obj is None:
1737 return None
1738
1739def getObject(obj, klass):
1740 if isinstance(obj, int):
1741 return klass(obj)
1742 elif isinstance(obj, long):
1743 return klass(int(obj))
1744 elif isinstance(obj, str):
1745 return klass(int(obj))
1746 elif obj is None:
1747 return None
1748 else:
1749 return obj
1750
1751__all__ = ['NoDefault', 'SQLObject',
1752 'SQLObjectIntegrityError', 'SQLObjectNotFound',
1753 'getID', 'getObject', 'sqlhub', 'sqlmeta',
1754 ]