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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
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, 4, 0):
0047 raise ImportError, "SQLObject requires Python 2.4.0 or later"
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_postponed_local = local()
0055
0056NoDefault = sqlbuilder.NoDefault
0057
0058class SQLObjectNotFound(LookupError): pass
0059class SQLObjectIntegrityError(Exception): pass
0060
0061def makeProperties(obj):
0062 """
0063 This function takes a dictionary of methods and finds
0064 methods named like:
0065 * _get_attr
0066 * _set_attr
0067 * _del_attr
0068 * _doc_attr
0069 Except for _doc_attr, these should be methods. It
0070 then creates properties from these methods, like
0071 property(_get_attr, _set_attr, _del_attr, _doc_attr).
0072 Missing methods are okay.
0073 """
0074
0075 if isinstance(obj, dict):
0076 def setFunc(var, value):
0077 obj[var] = value
0078 d = obj
0079 else:
0080 def setFunc(var, value):
0081 setattr(obj, var, value)
0082 d = obj.__dict__
0083
0084 props = {}
0085 for var, value in d.items():
0086 if var.startswith('_set_'):
0087 props.setdefault(var[5:], {})['set'] = value
0088 elif var.startswith('_get_'):
0089 props.setdefault(var[5:], {})['get'] = value
0090 elif var.startswith('_del_'):
0091 props.setdefault(var[5:], {})['del'] = value
0092 elif var.startswith('_doc_'):
0093 props.setdefault(var[5:], {})['doc'] = value
0094 for var, setters in props.items():
0095 if len(setters) == 1 and 'doc' in setters:
0096 continue
0097 if var in d:
0098 if isinstance(d[var], (types.MethodType, types.FunctionType)):
0099 warnings.warn(
0100 "I tried to set the property %r, but it was "
0101 "already set, as a method (%r). Methods have "
0102 "significantly different semantics than properties, "
0103 "and this may be a sign of a bug in your code."
0104 % (var, d[var]))
0105 continue
0106 setFunc(var,
0107 property(setters.get('get'), setters.get('set'),
0108 setters.get('del'), setters.get('doc')))
0109
0110def unmakeProperties(obj):
0111 if isinstance(obj, dict):
0112 def delFunc(obj, var):
0113 del obj[var]
0114 d = obj
0115 else:
0116 delFunc = delattr
0117 d = obj.__dict__
0118
0119 for var, value in d.items():
0120 if isinstance(value, property):
0121 for prop in [value.fget, value.fset, value.fdel]:
0122 if prop and not prop.__name__ in d:
0123 delFunc(obj, var)
0124 break
0125
0126def findDependencies(name, registry=None):
0127 depends = []
0128 for klass in classregistry.registry(registry).allClasses():
0129 if findDependantColumns(name, klass):
0130 depends.append(klass)
0131 else:
0132 for join in klass.sqlmeta.joins:
0133 if isinstance(join, joins.SORelatedJoin) and join.otherClassName == name:
0134 depends.append(klass)
0135 break
0136 return depends
0137
0138def findDependantColumns(name, klass):
0139 depends = []
0140 for col in klass.sqlmeta.columnList:
0141 if col.foreignKey == name and col.cascade is not None:
0142 depends.append(col)
0143 return depends
0144
0145def _collectAttributes(cls, new_attrs, look_for_class):
0146 """Finds all attributes in `new_attrs` that are instances of
0147 `look_for_class`. The ``.name`` attribute is set for any matching objects.
0148 Returns them as a list.
0149
0150 """
0151 result = []
0152 for attr, value in new_attrs.items():
0153 if isinstance(value, look_for_class):
0154 value.name = attr
0155 delattr(cls, attr)
0156 result.append(value)
0157 return result
0158
0159class CreateNewSQLObject:
0160 """
0161 Dummy singleton to use in place of an ID, to signal we want
0162 a new object.
0163 """
0164 pass
0165
0166class sqlmeta(object):
0167
0168 """
0169 This object is the object we use to keep track of all sorts of
0170 information. Subclasses are made for each SQLObject subclass
0171 (dynamically if necessary), and instances are created to go
0172 alongside every SQLObject instance.
0173 """
0174
0175 table = None
0176 idName = None
0177 idSequence = None
0178
0179
0180
0181 idType = int
0182 style = None
0183 lazyUpdate = False
0184 defaultOrder = None
0185 cacheValues = True
0186 registry = None
0187 fromDatabase = False
0188
0189
0190 expired = False
0191
0192
0193
0194 columns = {}
0195 columnList = []
0196
0197
0198
0199
0200 columnDefinitions = {}
0201
0202
0203 indexes = []
0204 indexDefinitions = []
0205 joins = []
0206 joinDefinitions = []
0207
0208
0209 _unshared_attributes = ['table', 'columns', 'childName']
0210
0211
0212
0213
0214
0215
0216
0217
0218
0219
0220
0221
0222 _creating = False
0223 _obsolete = False
0224
0225
0226
0227 _perConnection = False
0228
0229
0230 parentClass = None
0231 childClasses = {}
0232 childName = None
0233
0234
0235 dirty = False
0236
0237
0238 dbEncoding = None
0239
0240 __metaclass__ = declarative.DeclarativeMeta
0241
0242 def __classinit__(cls, new_attrs):
0243 for attr in cls._unshared_attributes:
0244 if attr not in new_attrs:
0245 setattr(cls, attr, None)
0246 declarative.setup_attributes(cls, new_attrs)
0247
0248 def __init__(self, instance):
0249 self.instance = weakref.proxy(instance)
0250
0251 @classmethod
0252 def send(cls, signal, *args, **kw):
0253 events.send(signal, cls.soClass, *args, **kw)
0254
0255 @classmethod
0256 def setClass(cls, soClass):
0257 cls.soClass = soClass
0258 if not cls.style:
0259 cls.style = styles.defaultStyle
0260 try:
0261 if cls.soClass._connection and cls.soClass._connection.style:
0262 cls.style = cls.soClass._connection.style
0263 except AttributeError:
0264 pass
0265 if cls.table is None:
0266 cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__)
0267 if cls.idName is None:
0268 cls.idName = cls.style.idForTable(cls.table)
0269
0270
0271
0272
0273
0274
0275 cls._plainSetters = {}
0276 cls._plainGetters = {}
0277 cls._plainForeignSetters = {}
0278 cls._plainForeignGetters = {}
0279 cls._plainJoinGetters = {}
0280 cls._plainJoinAdders = {}
0281 cls._plainJoinRemovers = {}
0282
0283
0284
0285 cls.columns = {}
0286 cls.columnList = []
0287
0288 cls.columnDefinitions = cls.columnDefinitions.copy()
0289 cls.indexes = []
0290 cls.indexDefinitions = cls.indexDefinitions[:]
0291 cls.joins = []
0292 cls.joinDefinitions = cls.joinDefinitions[:]
0293
0294
0295
0296
0297
0298
0299
0300
0301
0302 @classmethod
0303 def addColumn(cls, columnDef, changeSchema=False, connection=None):
0304 post_funcs = []
0305 cls.send(events.AddColumnSignal, cls.soClass, connection,
0306 columnDef.name, columnDef, changeSchema, post_funcs)
0307 sqlmeta = cls
0308 soClass = cls.soClass
0309 del cls
0310 column = columnDef.withClass(soClass)
0311 name = column.name
0312 assert name != 'id', (
0313 "The 'id' column is implicit, and should not be defined as "
0314 "a column")
0315 assert name not in sqlmeta.columns, (
0316 "The class %s.%s already has a column %r (%r), you cannot "
0317 "add the column %r"
0318 % (soClass.__module__, soClass.__name__, name,
0319 sqlmeta.columnDefinitions[name], columnDef))
0320
0321
0322 parent_columns = []
0323 for base in soClass.__bases__:
0324 if hasattr(base, "sqlmeta"):
0325 parent_columns.extend(base.sqlmeta.columns.keys())
0326 if hasattr(soClass, name):
0327 assert (name in parent_columns) or (name == "childName"), (
0328 "The class %s.%s already has a variable or method %r, you cannot "
0329 "add the column %r"
0330 % (soClass.__module__, soClass.__name__, name, name))
0331 sqlmeta.columnDefinitions[name] = columnDef
0332 sqlmeta.columns[name] = column
0333
0334 sqlmeta.columnList.append(column)
0335
0336
0337
0338
0339
0340
0341
0342
0343 if sqlmeta.cacheValues:
0344
0345
0346 getter = eval('lambda self: self._SO_loadValue(%s)' % repr(instanceName(name)))
0347
0348 else:
0349
0350
0351
0352 getter = eval('lambda self: self._SO_getValue(%s)' % repr(name))
0353 setattr(soClass, rawGetterName(name), getter)
0354
0355
0356
0357
0358 if not hasattr(soClass, getterName(name)) or (name == 'childName'):
0359 setattr(soClass, getterName(name), getter)
0360 sqlmeta._plainGetters[name] = 1
0361
0362
0363
0364
0365
0366
0367
0368
0369
0370
0371 if not column.immutable:
0372
0373 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))
0374 setattr(soClass, '_SO_from_python_%s' % name, column.from_python)
0375 setattr(soClass, '_SO_to_python_%s' % name, column.to_python)
0376 setattr(soClass, rawSetterName(name), setter)
0377
0378 if not hasattr(soClass, setterName(name)) or (name == 'childName'):
0379 setattr(soClass, setterName(name), setter)
0380
0381
0382
0383 sqlmeta._plainSetters[name] = 1
0384
0385
0386
0387
0388
0389
0390 if column.foreignKey:
0391
0392
0393
0394
0395 origName = column.origName
0396 if sqlmeta.cacheValues:
0397
0398
0399 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)))
0400 else:
0401
0402 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)))
0403 setattr(soClass, rawGetterName(origName), getter)
0404
0405
0406 if not hasattr(soClass, getterName(origName)):
0407 setattr(soClass, getterName(origName), getter)
0408 sqlmeta._plainForeignGetters[origName] = 1
0409
0410 if not column.immutable:
0411
0412
0413 setter = eval('lambda self, val: setattr(self, %s, self._SO_getID(val, %s))' % (repr(name), column.refColumn and repr(column.refColumn)))
0414 setattr(soClass, rawSetterName(origName), setter)
0415 if not hasattr(soClass, setterName(origName)):
0416 setattr(soClass, setterName(origName), setter)
0417 sqlmeta._plainForeignSetters[origName] = 1
0418
0419 classregistry.registry(sqlmeta.registry).addClassCallback(
0420 column.foreignKey,
0421 lambda foreign, me, attr: setattr(me, attr, foreign),
0422 soClass, '_SO_class_%s' % column.foreignKey)
0423
0424 if column.alternateMethodName:
0425 func = eval('lambda cls, val, connection=None: cls._SO_fetchAlternateID(%s, %s, val, connection=connection)' % (repr(column.name), repr(column.dbName)))
0426 setattr(soClass, column.alternateMethodName, classmethod(func))
0427
0428 if changeSchema:
0429 conn = connection or soClass._connection
0430 conn.addColumn(sqlmeta.table, column)
0431
0432 if soClass._SO_finishedClassCreation:
0433 makeProperties(soClass)
0434
0435 for func in post_funcs:
0436 func(soClass, column)
0437
0438 @classmethod
0439 def addColumnsFromDatabase(sqlmeta, connection=None):
0440 soClass = sqlmeta.soClass
0441 conn = connection or soClass._connection
0442 for columnDef in conn.columnsFromSchema(sqlmeta.table, soClass):
0443 if columnDef.name not in sqlmeta.columnDefinitions:
0444 if isinstance(columnDef.name, unicode):
0445 columnDef.name = columnDef.name.encode('ascii')
0446 sqlmeta.addColumn(columnDef)
0447
0448 @classmethod
0449 def delColumn(cls, column, changeSchema=False, connection=None):
0450 sqlmeta = cls
0451 soClass = sqlmeta.soClass
0452 if isinstance(column, str):
0453 if column in sqlmeta.columns:
0454 column = sqlmeta.columns[column]
0455 elif column+'ID' in sqlmeta.columns:
0456 column = sqlmeta.columns[column+'ID']
0457 else:
0458 raise ValueError('Unknown column ' + column)
0459 if isinstance(column, col.Col):
0460 for c in sqlmeta.columns.values():
0461 if column is c.columnDef:
0462 column = c
0463 break
0464 else:
0465 raise IndexError(
0466 "Column with definition %r not found" % column)
0467 post_funcs = []
0468 cls.send(events.DeleteColumnSignal, cls.soClass, connection,
0469 column.name, column, post_funcs)
0470 name = column.name
0471 del sqlmeta.columns[name]
0472 del sqlmeta.columnDefinitions[name]
0473 sqlmeta.columnList.remove(column)
0474 delattr(soClass, rawGetterName(name))
0475 if name in sqlmeta._plainGetters:
0476 delattr(soClass, getterName(name))
0477 delattr(soClass, rawSetterName(name))
0478 if name in sqlmeta._plainSetters:
0479 delattr(soClass, setterName(name))
0480 if column.foreignKey:
0481 delattr(soClass, rawGetterName(soClass.sqlmeta.style.instanceIDAttrToAttr(name)))
0482 if name in sqlmeta._plainForeignGetters:
0483 delattr(soClass, getterName(name))
0484 delattr(soClass, rawSetterName(soClass.sqlmeta.style.instanceIDAttrToAttr(name)))
0485 if name in sqlmeta._plainForeignSetters:
0486 delattr(soClass, setterName(name))
0487 if column.alternateMethodName:
0488 delattr(soClass, column.alternateMethodName)
0489
0490 if changeSchema:
0491 conn = connection or soClass._connection
0492 conn.delColumn(sqlmeta, column)
0493
0494 if soClass._SO_finishedClassCreation:
0495 unmakeProperties(soClass)
0496 makeProperties(soClass)
0497
0498 for func in post_funcs:
0499 func(soClass, column)
0500
0501
0502
0503
0504
0505 @classmethod
0506 def addJoin(cls, joinDef):
0507 sqlmeta = cls
0508 soClass = cls.soClass
0509
0510
0511
0512 join = joinDef.withClass(soClass)
0513 meth = join.joinMethodName
0514
0515 sqlmeta.joins.append(join)
0516 index = len(sqlmeta.joins)-1
0517 if joinDef not in sqlmeta.joinDefinitions:
0518 sqlmeta.joinDefinitions.append(joinDef)
0519
0520
0521
0522
0523 func = eval('lambda self: self.sqlmeta.joins[%i].performJoin(self)' % index)
0524
0525
0526 setattr(soClass, rawGetterName(meth), func)
0527 if not hasattr(soClass, getterName(meth)):
0528 setattr(soClass, getterName(meth), func)
0529 sqlmeta._plainJoinGetters[meth] = 1
0530
0531
0532
0533 if hasattr(join, 'remove'):
0534
0535
0536 func = eval('lambda self, obj: self.sqlmeta.joins[%i].remove(self, obj)' % index)
0537 setattr(soClass, '_SO_remove' + join.addRemoveName, func)
0538 if not hasattr(soClass, 'remove' + join.addRemoveName):
0539 setattr(soClass, 'remove' + join.addRemoveName, func)
0540 sqlmeta._plainJoinRemovers[meth] = 1
0541
0542
0543 if hasattr(join, 'add'):
0544
0545 func = eval('lambda self, obj: self.sqlmeta.joins[%i].add(self, obj)' % index)
0546 setattr(soClass, '_SO_add' + join.addRemoveName, func)
0547 if not hasattr(soClass, 'add' + join.addRemoveName):
0548 setattr(soClass, 'add' + join.addRemoveName, func)
0549 sqlmeta._plainJoinAdders[meth] = 1
0550
0551 if soClass._SO_finishedClassCreation:
0552 makeProperties(soClass)
0553
0554 @classmethod
0555 def delJoin(sqlmeta, joinDef):
0556 soClass = sqlmeta.soClass
0557 for join in sqlmeta.joins:
0558
0559
0560 if join is None:
0561 continue
0562 if joinDef is join.joinDef:
0563 break
0564 else:
0565 raise IndexError(
0566 "Join %r not found in class %r (from %r)"
0567 % (joinDef, soClass, sqlmeta.joins))
0568 meth = join.joinMethodName
0569 sqlmeta.joinDefinitions.remove(joinDef)
0570 for i in range(len(sqlmeta.joins)):
0571 if sqlmeta.joins[i] is join:
0572
0573
0574 sqlmeta.joins[i] = None
0575 delattr(soClass, rawGetterName(meth))
0576 if meth in sqlmeta._plainJoinGetters:
0577 delattr(soClass, getterName(meth))
0578 if hasattr(join, 'remove'):
0579 delattr(soClass, '_SO_remove' + join.addRemovePrefix)
0580 if meth in sqlmeta._plainJoinRemovers:
0581 delattr(soClass, 'remove' + join.addRemovePrefix)
0582 if hasattr(join, 'add'):
0583 delattr(soClass, '_SO_add' + join.addRemovePrefix)
0584 if meth in sqlmeta._plainJoinAdders:
0585 delattr(soClass, 'add' + join.addRemovePrefix)
0586
0587 if soClass._SO_finishedClassCreation:
0588 unmakeProperties(soClass)
0589 makeProperties(soClass)
0590
0591
0592
0593
0594
0595 @classmethod
0596 def addIndex(cls, indexDef):
0597 cls.indexDefinitions.append(indexDef)
0598 index = indexDef.withClass(cls.soClass)
0599 cls.indexes.append(index)
0600 setattr(cls.soClass, index.name, index)
0601
0602
0603
0604
0605
0606 @classmethod
0607 def getColumns(sqlmeta):
0608 return sqlmeta.columns.copy()
0609
0610 def asDict(self):
0611 """
0612 Return the object as a dictionary of columns to values.
0613 """
0614 result = {}
0615 for key in self.getColumns():
0616 result[key] = getattr(self.instance, key)
0617 result['id'] = self.instance.id
0618 return result
0619
0620 @classmethod
0621 def expireAll(sqlmeta, connection=None):
0622 """
0623 Expire all instances of this class.
0624 """
0625 soClass = sqlmeta.soClass
0626 connection = connection or soClass._connection
0627 cache_set = connection.cache
0628 cache_set.weakrefAll(soClass)
0629 for item in cache_set.getAll(soClass):
0630 item.expire()
0631
0632
0633sqlhub = dbconnection.ConnectionHub()
0634
0635class _sqlmeta_attr(object):
0636
0637 def __init__(self, name, deprecation_level):
0638 self.name = name
0639 self.deprecation_level = deprecation_level
0640
0641 def __get__(self, obj, type=None):
0642 if self.deprecation_level is not None:
0643 deprecated(
0644 'Use of this attribute should be replaced with '
0645 '.sqlmeta.%s' % self.name, level=self.deprecation_level)
0646 return getattr((type or obj).sqlmeta, self.name)
0647
0648
0649
0650
0651
0652
0653warnings_level = 1
0654exception_level = None
0655
0656
0657
0658
0659
0660def deprecated(message, level=1, stacklevel=2):
0661 if exception_level is not None and exception_level <= level:
0662 raise NotImplementedError(message)
0663 if warnings_level is not None and warnings_level <= level:
0664 warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
0665
0666
0667
0668
0669def setDeprecationLevel(warning=1, exception=None):
0670 """
0671 Set the deprecation level for SQLObject. Low levels are more
0672 actively being deprecated. Any warning at a level at or below
0673 ``warning`` will give a warning. Any warning at a level at or
0674 below ``exception`` will give an exception. You can use a higher
0675 ``exception`` level for tests to help upgrade your code. ``None``
0676 for either value means never warn or raise exceptions.
0677
0678 The levels currently mean:
0679
0680 1) Deprecated in current version (0.9). Will be removed in next
0681 version (0.10)
0682
0683 2) Planned to deprecate in next version, remove later.
0684
0685 3) Planned to deprecate sometime, remove sometime much later ;)
0686
0687 As the SQLObject versions progress, the deprecation level of
0688 specific features will go down, indicating the advancing nature of
0689 the feature's doom. We'll try to keep features at 1 for a major
0690 revision.
0691
0692 As time continues there may be a level 0, which will give a useful
0693 error message (better than ``AttributeError``) but where the
0694 feature has been fully removed.
0695 """
0696 global warnings_level, exception_level
0697 warnings_level = warning
0698 exception_level = exception
0699
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 def __getstate__(self):
1674 if self.sqlmeta._perConnection:
1675 from pickle import PicklingError
1676 raise PicklingError('Cannot pickle an SQLObject instance that has a per-instance connection')
1677 d = self.__dict__.copy()
1678 del d['sqlmeta']
1679 del d['_SO_writeLock']
1680 return d
1681
1682 def __setstate__(self, d):
1683 self.__init__(_SO_fetch_no_create=1)
1684 self._SO_writeLock = threading.Lock()
1685 self.__dict__.update(d)
1686 self.__class__._connection.cache.put(self.id, self.__class__, self)
1687
1688
1689def setterName(name):
1690 return '_set_%s' % name
1691def rawSetterName(name):
1692 return '_SO_set_%s' % name
1693def getterName(name):
1694 return '_get_%s' % name
1695def rawGetterName(name):
1696 return '_SO_get_%s' % name
1697def instanceName(name):
1698 return '_SO_val_%s' % name
1699
1700
1701
1702
1703
1704
1705def getID(obj, refColumn=None):
1706 if isinstance(obj, SQLObject):
1707 return getattr(obj, refColumn or 'id')
1708 elif isinstance(obj, int):
1709 return obj
1710 elif isinstance(obj, long):
1711 return int(obj)
1712 elif isinstance(obj, str):
1713 try:
1714 return int(obj)
1715 except ValueError:
1716 return obj
1717 elif obj is None:
1718 return None
1719
1720def getObject(obj, klass):
1721 if isinstance(obj, int):
1722 return klass(obj)
1723 elif isinstance(obj, long):
1724 return klass(int(obj))
1725 elif isinstance(obj, str):
1726 return klass(int(obj))
1727 elif obj is None:
1728 return None
1729 else:
1730 return obj
1731
1732__all__ = ['NoDefault', 'SQLObject', 'sqlmeta',
1733 'getID', 'getObject',
1734 'SQLObjectNotFound', 'sqlhub',
1735 'setDeprecationLevel']