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 values = [(self.sqlmeta.columns[v[0]].dbName, v[1])
0989 for v in self._SO_createValues.items()]
0990 self._connection._SO_update(self, values)
0991 self.sqlmeta.dirty = False
0992 self._SO_createValues = {}
0993 finally:
0994 self._SO_writeLock.release()
0995
0996 post_funcs = []
0997 self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
0998 for func in post_funcs:
0999 func(self)
1000
1001 def expire(self):
1002 if self.sqlmeta.expired:
1003 return
1004 self._SO_writeLock.acquire()
1005 try:
1006 if self.sqlmeta.expired:
1007 return
1008 for column in self.sqlmeta.columnList:
1009 delattr(self, instanceName(column.name))
1010 self.sqlmeta.expired = True
1011 self._connection.cache.expire(self.id, self.__class__)
1012 self._SO_createValues = {}
1013 finally:
1014 self._SO_writeLock.release()
1015
1016 def _SO_setValue(self, name, value, from_python, to_python):
1017
1018
1019
1020
1021
1022
1023
1024 d = {name: value}
1025 if not self.sqlmeta._creating and not getattr(self.sqlmeta, "row_update_sig_suppress", False):
1026 self.sqlmeta.send(events.RowUpdateSignal, self, d)
1027 if len(d) != 1 or name not in d:
1028
1029
1030 self.sqlmeta.row_update_sig_suppress = True
1031 self.set(**d)
1032 del self.sqlmeta.row_update_sig_suppress
1033 value = d[name]
1034 if from_python:
1035 dbValue = from_python(value, self._SO_validatorState)
1036 else:
1037 dbValue = value
1038 if to_python:
1039 value = to_python(dbValue, self._SO_validatorState)
1040 if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1041 self.sqlmeta.dirty = True
1042 self._SO_createValues[name] = dbValue
1043 setattr(self, instanceName(name), value)
1044 return
1045
1046 self._connection._SO_update(
1047 self, [(self.sqlmeta.columns[name].dbName,
1048 dbValue)])
1049
1050 if self.sqlmeta.cacheValues:
1051 setattr(self, instanceName(name), value)
1052
1053 post_funcs = []
1054 self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1055 for func in post_funcs:
1056 func(self)
1057
1058 def set(self, _suppress_set_sig=False, **kw):
1059 if not self.sqlmeta._creating and not getattr(self.sqlmeta, "row_update_sig_suppress", False) and not _suppress_set_sig:
1060 self.sqlmeta.send(events.RowUpdateSignal, self, kw)
1061
1062
1063
1064
1065
1066
1067 is_column = lambda _c: _c in self.sqlmeta._plainSetters
1068 f_is_column = lambda item: is_column(item[0])
1069 f_not_column = lambda item: not is_column(item[0])
1070 items = kw.items()
1071 extra = dict(filter(f_not_column, items))
1072 kw = dict(filter(f_is_column, items))
1073
1074
1075 if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1076 for name, value in kw.items():
1077 from_python = getattr(self, '_SO_from_python_%s' % name, None)
1078 if from_python:
1079 kw[name] = dbValue = from_python(value, self._SO_validatorState)
1080 else:
1081 dbValue = value
1082 to_python = getattr(self, '_SO_to_python_%s' % name, None)
1083 if to_python:
1084 value = to_python(dbValue, self._SO_validatorState)
1085 setattr(self, instanceName(name), value)
1086
1087 self._SO_createValues.update(kw)
1088
1089 for name, value in extra.items():
1090 try:
1091 getattr(self.__class__, name)
1092 except AttributeError:
1093 if name not in self.sqlmeta.columns:
1094 raise TypeError, "%s.set() got an unexpected keyword argument %s" % (self.__class__.__name__, name)
1095 try:
1096 setattr(self, name, value)
1097 except AttributeError, e:
1098 raise AttributeError, '%s (with attribute %r)' % (e, name)
1099
1100 self.sqlmeta.dirty = True
1101 return
1102
1103 self._SO_writeLock.acquire()
1104
1105 try:
1106
1107
1108
1109
1110
1111
1112
1113
1114 toUpdate = {}
1115 for name, value in kw.items():
1116 from_python = getattr(self, '_SO_from_python_%s' % name, None)
1117 if from_python:
1118 dbValue = from_python(value, self._SO_validatorState)
1119 else:
1120 dbValue = value
1121 to_python = getattr(self, '_SO_to_python_%s' % name, None)
1122 if to_python:
1123 value = to_python(dbValue, self._SO_validatorState)
1124 if self.sqlmeta.cacheValues:
1125 setattr(self, instanceName(name), value)
1126 toUpdate[name] = dbValue
1127 for name, value in extra.items():
1128 try:
1129 getattr(self.__class__, name)
1130 except AttributeError:
1131 if name not in self.sqlmeta.columns:
1132 raise TypeError, "%s.set() got an unexpected keyword argument %s" % (self.__class__.__name__, name)
1133 try:
1134 setattr(self, name, value)
1135 except AttributeError, e:
1136 raise AttributeError, '%s (with attribute %r)' % (e, name)
1137
1138 if toUpdate:
1139 args = [(self.sqlmeta.columns[name].dbName, value)
1140 for name, value in toUpdate.items()]
1141 self._connection._SO_update(self, args)
1142 finally:
1143 self._SO_writeLock.release()
1144
1145 post_funcs = []
1146 self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1147 for func in post_funcs:
1148 func(self)
1149
1150 def _SO_selectInit(self, row):
1151 for col, colValue in zip(self.sqlmeta.columnList, row):
1152 if col.to_python:
1153 colValue = col.to_python(colValue, self._SO_validatorState)
1154 setattr(self, instanceName(col.name), colValue)
1155
1156 def _SO_getValue(self, name):
1157
1158 assert not self.sqlmeta._obsolete, (
1159 "%s with id %s has become obsolete" % (self.__class__.__name__, self.id))
1161
1162
1163 column = self.sqlmeta.columns[name]
1164 results = self._connection._SO_selectOne(self, [column.dbName])
1165
1166 assert results != None, "%s with id %s is not in the database" % (self.__class__.__name__, self.id)
1168 value = results[0]
1169 if column.to_python:
1170 value = column.to_python(value, self._SO_validatorState)
1171 return value
1172
1173 def _SO_foreignKey(self, value, joinClass, idName=None):
1174 if value is None:
1175 return None
1176 if self.sqlmeta._perConnection:
1177 connection = self._connection
1178 else:
1179 connection = None
1180 if idName is None:
1181 return joinClass.get(value, connection=connection)
1182 return joinClass.select(
1183 getattr(joinClass.q, idName)==value, connection=connection).getOne()
1184
1185 def __init__(self, **kw):
1186
1187
1188
1189
1190 try:
1191 _postponed_local.postponed_calls
1192 postponed_created = False
1193 except AttributeError:
1194 _postponed_local.postponed_calls = []
1195 postponed_created = True
1196
1197 try:
1198
1199
1200
1201 self.sqlmeta = self.__class__.sqlmeta(self)
1202
1203
1204
1205 if '_SO_fetch_no_create' in kw:
1206 return
1207
1208 post_funcs = []
1209 self.sqlmeta.send(events.RowCreateSignal, self, kw, post_funcs)
1210
1211
1212 if 'connection' in kw:
1213 connection = kw.pop('connection')
1214 if getattr(self, '_connection', None) is not connection:
1215 self._connection = connection
1216 self.sqlmeta._perConnection = True
1217
1218 self._SO_writeLock = threading.Lock()
1219
1220 if 'id' in kw:
1221 id = self.sqlmeta.idType(kw['id'])
1222 del kw['id']
1223 else:
1224 id = None
1225
1226 self._create(id, **kw)
1227
1228 for func in post_funcs:
1229 func(self)
1230 finally:
1231
1232
1233
1234 if postponed_created:
1235 try:
1236 for func in _postponed_local.postponed_calls:
1237 func()
1238 finally:
1239 del _postponed_local.postponed_calls
1240
1241 def _create(self, id, **kw):
1242
1243 self.sqlmeta._creating = True
1244 self._SO_createValues = {}
1245 self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1246
1247
1248
1249 for column in self.sqlmeta.columnList:
1250
1251
1252
1253 if column.name not in kw and column.foreignName not in kw:
1254 default = column.default
1255
1256
1257
1258 if default is NoDefault:
1259 if column.defaultSQL is None:
1260 raise TypeError, "%s() did not get expected keyword argument '%s'" % (self.__class__.__name__, column.name)
1261 else:
1262
1263
1264 continue
1265
1266
1267
1268
1269 kw[column.name] = default
1270
1271 self.set(**kw)
1272
1273
1274 self._SO_finishCreate(id)
1275
1276 def _SO_finishCreate(self, id=None):
1277
1278
1279
1280 setters = self._SO_createValues.items()
1281
1282 names = [self.sqlmeta.columns[v[0]].dbName for v in setters]
1283 values = [v[1] for v in setters]
1284
1285
1286
1287 self.sqlmeta.dirty = False
1288 if not self.sqlmeta.lazyUpdate:
1289 del self._SO_createValues
1290 else:
1291 self._SO_createValues = {}
1292 del self.sqlmeta._creating
1293
1294
1295
1296
1297 id = self._connection.queryInsertID(self,
1298 id, names, values)
1299 cache = self._connection.cache
1300 cache.created(id, self.__class__, self)
1301 self._init(id)
1302 post_funcs = []
1303 kw = dict([('class', self.__class__), ('id', id)])
1304 def _send_RowCreatedSignal():
1305 self.sqlmeta.send(events.RowCreatedSignal, self, kw, post_funcs)
1306 for func in post_funcs:
1307 func(self)
1308 _postponed_local.postponed_calls.append(_send_RowCreatedSignal)
1309
1310 def _SO_getID(self, obj, refColumn=None):
1311 return getID(obj, refColumn)
1312
1313 @classmethod
1314 def _findAlternateID(cls, name, dbName, value, connection=None):
1315 if isinstance(name, str):
1316 name = (name,)
1317 value = (value,)
1318 if len(name) != len(value):
1319 raise ValueError, "'column' and 'value' tuples must be of the same size"
1320 new_value = []
1321 for n, v in zip(name, value):
1322 from_python = getattr(cls, '_SO_from_python_' + n)
1323 if from_python:
1324 v = from_python(v, sqlbuilder.SQLObjectState(cls, connection=connection))
1325 new_value.append(v)
1326 condition = sqlbuilder.AND(*[getattr(cls.q, n)==v for n,v in zip(name, new_value)])
1327 return (connection or cls._connection)._SO_selectOneAlt(
1328 cls,
1329 [cls.sqlmeta.idName] +
1330 [column.dbName for column in cls.sqlmeta.columnList],
1331 condition), None
1332
1333 @classmethod
1334 def _SO_fetchAlternateID(cls, name, dbName, value, connection=None, idxName=None):
1335 result, obj = cls._findAlternateID(name, dbName, value, connection)
1336 if not result:
1337 if idxName is None:
1338 raise SQLObjectNotFound, "The %s by alternateID %s = %s does not exist" % (cls.__name__, name, repr(value))
1339 else:
1340 names = []
1341 for i in xrange(len(name)):
1342 names.append("%s = %s" % (name[i], repr(value[i])))
1343 names = ', '.join(names)
1344 raise SQLObjectNotFound, "The %s by unique index %s(%s) does not exist" % (cls.__name__, idxName, names)
1345 if obj:
1346 return obj
1347 if connection:
1348 obj = cls.get(result[0], connection=connection, selectResults=result[1:])
1349 else:
1350 obj = cls.get(result[0], selectResults=result[1:])
1351 return obj
1352
1353 @classmethod
1354 def _SO_depends(cls):
1355 return findDependencies(cls.__name__, cls.sqlmeta.registry)
1356
1357 @classmethod
1358 def select(cls, clause=None, clauseTables=None,
1359 orderBy=NoDefault, limit=None,
1360 lazyColumns=False, reversed=False,
1361 distinct=False, connection=None,
1362 join=None, forUpdate=False):
1363 return cls.SelectResultsClass(cls, clause,
1364 clauseTables=clauseTables,
1365 orderBy=orderBy,
1366 limit=limit,
1367 lazyColumns=lazyColumns,
1368 reversed=reversed,
1369 distinct=distinct,
1370 connection=connection,
1371 join=join, forUpdate=forUpdate)
1372
1373 @classmethod
1374 def selectBy(cls, connection=None, **kw):
1375 conn = connection or cls._connection
1376 return cls.SelectResultsClass(cls,
1377 conn._SO_columnClause(cls, kw),
1378 connection=conn)
1379
1380 @classmethod
1381 def tableExists(cls, connection=None):
1382 conn = connection or cls._connection
1383 return conn.tableExists(cls.sqlmeta.table)
1384
1385 @classmethod
1386 def dropTable(cls, ifExists=False, dropJoinTables=True, cascade=False,
1387 connection=None):
1388 conn = connection or cls._connection
1389 if ifExists and not cls.tableExists(connection=conn):
1390 return
1391 extra_sql = []
1392 post_funcs = []
1393 cls.sqlmeta.send(events.DropTableSignal, cls, connection,
1394 extra_sql, post_funcs)
1395 conn.dropTable(cls.sqlmeta.table, cascade)
1396 if dropJoinTables:
1397 cls.dropJoinTables(ifExists=ifExists, connection=conn)
1398 for sql in extra_sql:
1399 connection.query(sql)
1400 for func in post_funcs:
1401 func(cls, conn)
1402
1403 @classmethod
1404 def createTable(cls, ifNotExists=False, createJoinTables=True,
1405 createIndexes=True, applyConstraints=True,
1406 connection=None):
1407 conn = connection or cls._connection
1408 if ifNotExists and cls.tableExists(connection=conn):
1409 return
1410 extra_sql = []
1411 post_funcs = []
1412 cls.sqlmeta.send(events.CreateTableSignal, cls, connection,
1413 extra_sql, post_funcs)
1414 constraints = conn.createTable(cls)
1415 if applyConstraints:
1416 for constraint in constraints:
1417 conn.query(constraint)
1418 else:
1419 extra_sql.extend(constraints)
1420 if createJoinTables:
1421 cls.createJoinTables(ifNotExists=ifNotExists,
1422 connection=conn)
1423 if createIndexes:
1424 cls.createIndexes(ifNotExists=ifNotExists,
1425 connection=conn)
1426 for func in post_funcs:
1427 func(cls, conn)
1428 return extra_sql
1429
1430 @classmethod
1431 def createTableSQL(cls, createJoinTables=True, createIndexes=True,
1432 connection=None):
1433 conn = connection or cls._connection
1434 sql, constraints = conn.createTableSQL(cls)
1435 if createJoinTables:
1436 join_sql = cls.createJoinTablesSQL(connection=conn)
1437 if join_sql:
1438 sql += ';\n' + join_sql
1439 if createIndexes:
1440 index_sql = cls.createIndexesSQL(connection=conn)
1441 if index_sql:
1442 sql += ';\n' + index_sql
1443 return sql, constraints
1444
1445 @classmethod
1446 def createJoinTables(cls, ifNotExists=False, connection=None):
1447 conn = connection or cls._connection
1448 for join in cls._getJoinsToCreate():
1449 if (ifNotExists and
1450 conn.tableExists(join.intermediateTable)):
1451 continue
1452 conn._SO_createJoinTable(join)
1453
1454 @classmethod
1455 def createJoinTablesSQL(cls, connection=None):
1456 conn = connection or cls._connection
1457 sql = []
1458 for join in cls._getJoinsToCreate():
1459 sql.append(conn._SO_createJoinTableSQL(join))
1460 return ';\n'.join(sql)
1461
1462 @classmethod
1463 def createIndexes(cls, ifNotExists=False, connection=None):
1464 conn = connection or cls._connection
1465 for index in cls.sqlmeta.indexes:
1466 if not index:
1467 continue
1468 conn._SO_createIndex(cls, index)
1469
1470 @classmethod
1471 def createIndexesSQL(cls, connection=None):
1472 conn = connection or cls._connection
1473 sql = []
1474 for index in cls.sqlmeta.indexes:
1475 if not index:
1476 continue
1477 sql.append(conn.createIndexSQL(cls, index))
1478 return ';\n'.join(sql)
1479
1480 @classmethod
1481 def _getJoinsToCreate(cls):
1482 joins = []
1483 for join in cls.sqlmeta.joins:
1484 if not join:
1485 continue
1486 if not join.hasIntermediateTable() or not getattr(join, 'createRelatedTable', True):
1487 continue
1488 if join.soClass.__name__ > join.otherClass.__name__:
1489 continue
1490 joins.append(join)
1491 return joins
1492
1493 @classmethod
1494 def dropJoinTables(cls, ifExists=False, connection=None):
1495 conn = connection or cls._connection
1496 for join in cls.sqlmeta.joins:
1497 if not join:
1498 continue
1499 if not join.hasIntermediateTable() or not getattr(join, 'createRelatedTable', True):
1500 continue
1501 if join.soClass.__name__ > join.otherClass.__name__:
1502 continue
1503 if ifExists and not conn.tableExists(join.intermediateTable):
1505 continue
1506 conn._SO_dropJoinTable(join)
1507
1508 @classmethod
1509 def clearTable(cls, connection=None, clearJoinTables=True):
1510
1511
1512 conn = connection or cls._connection
1513 conn.clearTable(cls.sqlmeta.table)
1514 if clearJoinTables:
1515 for join in cls._getJoinsToCreate():
1516 conn.clearTable(join.intermediateTable)
1517
1518 def destroySelf(self):
1519 post_funcs = []
1520 self.sqlmeta.send(events.RowDestroySignal, self, post_funcs)
1521
1522
1523 klass = self.__class__
1524
1525
1526 for join in klass.sqlmeta.joins:
1527 if isinstance(join, joins.SORelatedJoin):
1528 q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable, join.joinColumn, self.id)
1529 self._connection.query(q)
1530
1531 depends = []
1532 depends = self._SO_depends()
1533 for k in depends:
1534
1535 for join in k.sqlmeta.joins:
1536 if isinstance(join, joins.SORelatedJoin) and join.otherClassName == klass.__name__:
1537 q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable, join.otherColumn, self.id)
1538 self._connection.query(q)
1539
1540 cols = findDependantColumns(klass.__name__, k)
1541
1542
1543 if len(cols) == 0:
1544 continue
1545
1546 query = []
1547 delete = setnull = restrict = False
1548 for col in cols:
1549 if col.cascade == False:
1550
1551 restrict = True
1552 query.append(getattr(k.q, col.name) == self.id)
1553 if col.cascade == 'null':
1554 setnull = col.name
1555 elif col.cascade:
1556 delete = True
1557 assert delete or setnull or restrict, (
1558 "Class %s depends on %s accoriding to "
1559 "findDependantColumns, but this seems inaccurate"
1560 % (k, klass))
1561 query = sqlbuilder.OR(*query)
1562 results = k.select(query, connection=self._connection)
1563 if restrict:
1564 if results.count():
1565
1566
1567 raise SQLObjectIntegrityError, (
1568 "Tried to delete %s::%s but "
1569 "table %s has a restriction against it" %
1570 (klass.__name__, self.id, k.__name__))
1571 else:
1572 for row in results:
1573 if delete:
1574 row.destroySelf()
1575 else:
1576 row.set(**{setnull: None})
1577
1578 self.sqlmeta._obsolete = True
1579 self._connection._SO_delete(self)
1580 self._connection.cache.expire(self.id, self.__class__)
1581
1582 for func in post_funcs:
1583 func(self)
1584
1585 post_funcs = []
1586 self.sqlmeta.send(events.RowDestroyedSignal, self, post_funcs)
1587 for func in post_funcs:
1588 func(self)
1589
1590 @classmethod
1591 def delete(cls, id, connection=None):
1592 obj = cls.get(id, connection=connection)
1593 obj.destroySelf()
1594
1595 @classmethod
1596 def deleteMany(cls, where=NoDefault, connection=None):
1597 conn = connection or cls._connection
1598 conn.query(conn.sqlrepr(sqlbuilder.Delete(cls.sqlmeta.table, where)))
1599
1600 @classmethod
1601 def deleteBy(cls, connection=None, **kw):
1602 conn = connection or cls._connection
1603 conn.query(conn.sqlrepr(sqlbuilder.Delete(cls.sqlmeta.table,
1604 conn._SO_columnClause(cls, kw))))
1605
1606 def __repr__(self):
1607 if not hasattr(self, 'id'):
1608
1609 return '<%s (not initialized)>' % self.__class__.__name__
1610 return '<%s %r %s>' % (self.__class__.__name__,
1612 self.id,
1613 ' '.join(['%s=%s' % (name, repr(value)) for name, value in self._reprItems()]))
1614
1615 def __sqlrepr__(self, db):
1616 return str(self.id)
1617
1618 @classmethod
1619 def sqlrepr(cls, value, connection=None):
1620 return (connection or cls._connection).sqlrepr(value)
1621
1622 @classmethod
1623 def coerceID(cls, value):
1624 if isinstance(value, cls):
1625 return value.id
1626 else:
1627 return cls.sqlmeta.idType(value)
1628
1629 def _reprItems(self):
1630 items = []
1631 for col in self.sqlmeta.columnList:
1632 value = getattr(self, col.name)
1633 r = repr(value)
1634 if len(r) > 20:
1635 value = r[:17] + "..." + r[-1]
1636 items.append((col.name, value))
1637 return items
1638
1639 @classmethod
1640 def setConnection(cls, value):
1641 if isinstance(value, basestring):
1642 value = dbconnection.connectionForURI(value)
1643 cls._connection = value
1644
1645 def tablesUsedImmediate(self):
1646 return [self.__class__.q]
1647
1648
1649
1650
1651 def __eq__(self, other):
1652 if self.__class__ is other.__class__:
1653 if self.id == other.id:
1654 return True
1655 return False
1656
1657 def __ne__(self, other):
1658 return not self.__eq__(other)
1659
1660 def __lt__(self, other):
1661 return NotImplemented
1662
1663 def __le__(self, other):
1664 return NotImplemented
1665
1666 def __gt__(self, other):
1667 return NotImplemented
1668
1669 def __ge__(self, other):
1670 return NotImplemented
1671
1672
1673
1674
1675 def __getstate__(self):
1676 if self.sqlmeta._perConnection:
1677 from pickle import PicklingError
1678 raise PicklingError('Cannot pickle an SQLObject instance that has a per-instance connection')
1679 if self.sqlmeta.lazyUpdate and self._SO_createValues:
1680 self.syncUpdate()
1681 d = self.__dict__.copy()
1682 del d['sqlmeta']
1683 del d['_SO_validatorState']
1684 del d['_SO_writeLock']
1685 del d['_SO_createValues']
1686 return d
1687
1688 def __setstate__(self, d):
1689 self.__init__(_SO_fetch_no_create=1)
1690 self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1691 self._SO_writeLock = threading.Lock()
1692 self._SO_createValues = {}
1693 self.__dict__.update(d)
1694 cls = self.__class__
1695 cache = self._connection.cache
1696 if cache.tryGet(self.id, cls) is not None:
1697 raise ValueError(
1698 "Cannot unpickle %s row with id=%s - a different instance with the id already exists in the cache" % (cls.__name__, self.id))
1699 cache.created(self.id, cls, self)
1700
1701
1702def setterName(name):
1703 return '_set_%s' % name
1704def rawSetterName(name):
1705 return '_SO_set_%s' % name
1706def getterName(name):
1707 return '_get_%s' % name
1708def rawGetterName(name):
1709 return '_SO_get_%s' % name
1710def instanceName(name):
1711 return '_SO_val_%s' % name
1712
1713
1714
1715
1716
1717
1718def getID(obj, refColumn=None):
1719 if isinstance(obj, SQLObject):
1720 return getattr(obj, refColumn or 'id')
1721 elif isinstance(obj, int):
1722 return obj
1723 elif isinstance(obj, long):
1724 return int(obj)
1725 elif isinstance(obj, str):
1726 try:
1727 return int(obj)
1728 except ValueError:
1729 return obj
1730 elif obj is None:
1731 return None
1732
1733def getObject(obj, klass):
1734 if isinstance(obj, int):
1735 return klass(obj)
1736 elif isinstance(obj, long):
1737 return klass(int(obj))
1738 elif isinstance(obj, str):
1739 return klass(int(obj))
1740 elif obj is None:
1741 return None
1742 else:
1743 return obj
1744
1745__all__ = ['NoDefault', 'SQLObject',
1746 'SQLObjectIntegrityError', 'SQLObjectNotFound',
1747 'getID', 'getObject', 'sqlhub', 'sqlmeta',
1748 ]